Updated on 2026-08-14

This commit is contained in:
Tangem 2018-12-04 11:05:16 +03:00
parent ebc3abcfe1
commit 48db52b2a9
99 changed files with 860 additions and 895 deletions

View file

@ -68,8 +68,7 @@ public class LogFileProvider extends ContentProvider {
// Create & return a ParcelFileDescriptor pointing to the file
// Note: I don't care what mode they ask for - they're only getting
// read only
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(
fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
// Otherwise unrecognised Uri

View file

@ -3,7 +3,7 @@ package com.tangem.data;
import android.content.Context;
import android.util.Log;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import java.io.BufferedReader;
import java.io.BufferedWriter;

View file

@ -8,7 +8,7 @@ import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import com.tangem.data.db.PINStorage;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.presentation.activity.PinRequestActivity;
import java.io.IOException;

View file

@ -4,30 +4,22 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.util.Util;
import com.tangem.wallet.R;
import java.io.IOException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
//import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
@ -36,9 +28,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
/**
* HTTP

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -12,7 +11,7 @@ import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.LinkedTreeMap;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import java.io.IOException;

View file

@ -4,10 +4,9 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import org.spongycastle.util.encoders.Base64;

View file

@ -7,8 +7,8 @@ import android.util.Log;
import com.tangem.App;
import com.tangem.data.network.model.CardVerifyAndGetInfo;
import com.tangem.data.network.model.RateInfoResponse;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.util.Util;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.util.Util;
import java.io.InputStream;
import java.util.ArrayList;

View file

@ -3,8 +3,8 @@ package com.tangem.data.network;
import android.util.Log;
import com.tangem.App;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.Blockchain;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.bch.BitcoinCashNode;
import com.tangem.domain.wallet.btc.BitcoinNode;
import com.tangem.domain.wallet.btc.BitcoinNodeTestNet;

View file

@ -1,102 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
public class PurgeTask extends Thread {
public static final String TAG = PurgeTask.class.getSimpleName();
private String txOutAddress;
private Context mContext;
private TangemCard mCard;
private NfcManager mNfcManager;
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public PurgeTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mCard = card;
mContext = context;
mNfcManager = nfcManager;
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
// for Samsung's bugs - Workaround for the Samsung Galaxy S5 (since the first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start purge --]");
if (isCancelled) return;
if (mCard.getPauseBeforePIN2() > 0) {
mNotifications.onReadWait(mCard.getPauseBeforePIN2());
}
// try {
protocol.run_PurgeWallet(PINStorage.getPIN2());
// } finally {
// mNotifications.onReadWait(0);
// }
mNotifications.onReadProgress(protocol, 50);
protocol.run_Read();
mNotifications.onReadProgress(protocol, 100);
if (isCancelled)
return;
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
mNotifications.onReadWait(0);
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish purge --]");
mNotifications.onReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,173 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.util.Util;
import java.util.ArrayList;
public class ReadCardInfoTask extends Thread {
public static final String TAG = ReadCardInfoTask.class.getSimpleName();
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
private Context mContext;
private NfcManager mNfcManager;
// 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 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;
// ReadCardInfoTask.lastRead_UID = lastRead_UID;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mNotifications);
mNotifications.onReadStart(protocol);
try {
mNotifications.onReadProgress(protocol, 5);
byte[] UID = mIsoDep.getTag().getId();
String sUID = Util.byteArrayToHexString(UID);
if (!lastRead_UID.equals(sUID)) {
resetLastReadInfo();
}
Log.i(TAG, "[-- Start read card info --]");
if (isCancelled) return;
protocol.setPIN(PINStorage.getDefaultPIN());
protocol.clearReadResult();
if (lastRead_Encryption == null) {
Log.i(TAG, "Try get supported encryption mode");
protocol.run_GetSupportedEncryption();
} else {
Log.i(TAG, "Use already defined encryption mode: " + lastRead_Encryption.name());
protocol.getCard().encryptionMode = lastRead_Encryption;
}
if (protocol.haveReadResult()) {
//already have read result (obtained while get supported encryption), only read issuer data and define offline balance
protocol.parseReadResult();
protocol.run_ReadWriteIssuerData();
mNotifications.onReadProgress(protocol, 60);
PINStorage.setLastUsedPIN(protocol.getCard().getPIN());
} else {
//don't have read result - may be don't get supported encryption on this try, need encryption or need another PIN
if (lastRead_Encryption == null) {
// we try get supported encryption on this time
lastRead_Encryption = protocol.getCard().encryptionMode;
if (protocol.getCard().encryptionMode == TangemCard.EncryptionMode.None) {
// default pin not accepted
lastRead_UnsuccessfullPINs.add(PINStorage.getDefaultPIN());
}
}
boolean pinFound = false;
for (String PIN : PINStorage.getPINs()) {
Log.e(TAG, "PIN: " + PIN);
boolean skipPin = false;
for (int i = 0; i < lastRead_UnsuccessfullPINs.size(); i++) {
if (lastRead_UnsuccessfullPINs.get(i).equals(PIN)) {
skipPin = true;
break;
}
}
if (skipPin) {
Log.e(TAG, "Skip PIN - already checked before");
continue;
}
try {
protocol.setPIN(PIN);
if (protocol.getCard().encryptionMode != TangemCard.EncryptionMode.None) {
protocol.CreateProtocolKey();
}
protocol.run_Read();
mNotifications.onReadProgress(protocol, 60);
PINStorage.setLastUsedPIN(PIN);
pinFound = true;
protocol.getCard().setPIN(PIN);
break;
} catch (CardProtocol.TangemException_InvalidPIN e) {
Log.e(TAG, e.getMessage());
lastRead_UnsuccessfullPINs.add(PIN);
}
}
if (!pinFound) {
throw new CardProtocol.TangemException_InvalidPIN("No valid PIN found!");
}
}
protocol.run_CheckPIN2isDefault();
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish read card info --]");
mNotifications.onReadFinish(protocol);
}
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
mNfcManager.notifyReadResult(false);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,116 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.Firmwares;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import java.util.Arrays;
/**
* Created by dvol on 04.02.2018.
*/
public class VerifyCardTask extends Thread {
public static final String TAG = VerifyCardTask.class.getSimpleName();
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
private Context mContext;
private TangemCard mCard;
private NfcManager mNfcManager;
public VerifyCardTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mCard = card;
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mNfcManager = nfcManager;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start verify card --]");
if (isCancelled) return;
String PIN = mCard.getPIN();
protocol.setPIN(PIN);
protocol.run_Read(false);
PINStorage.setLastUsedPIN(PIN);
mNotifications.onReadProgress(protocol, 20);
if (isCancelled) return;
protocol.run_VerifyCard();
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, 80);
}
if (isCancelled) return;
Firmwares.VerifyCodeRecord record=Firmwares.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);
} finally {
Log.i(TAG, "[-- Finish verify card --]");
mNotifications.onReadFinish(protocol);
}
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -4,7 +4,7 @@ import android.app.Activity
import android.nfc.Tag
import android.os.Bundle
import com.tangem.domain.wallet.CoinData
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.data.TangemCard
import com.tangem.presentation.activity.EmptyWalletActivity
import com.tangem.presentation.activity.LoadedWalletActivity
import com.tangem.presentation.activity.MainActivity

View file

@ -11,21 +11,14 @@ import com.tangem.domain.wallet.btc.BitcoinOutputStream;
import com.tangem.domain.wallet.btc.BtcData;
import com.tangem.util.CryptoUtil;
import com.tangem.util.FormatUtil;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
public final class BTCUtils {

View file

@ -1,5 +1,6 @@
package com.tangem.domain.wallet;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.wallet.R;
public class BalanceValidator {

View file

@ -3,8 +3,8 @@ package com.tangem.domain.wallet;
import android.os.Bundle;
import android.util.Log;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.tangem.tangemcard.data.Blockchain;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class CoinData {
@ -12,6 +12,24 @@ public abstract class CoinData {
public CoinData() {
}
private String wallet;
public void setWallet(String wallet) {
this.wallet = wallet;
}
public String getWallet() {
return wallet;
}
public String getShortWalletString() {
if (wallet.length() < 22) {
return wallet;
} else {
return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length());
}
}
public boolean isBalanceReceived() {
return balanceReceived;
}
@ -23,6 +41,8 @@ public abstract class CoinData {
}
public void loadFromBundle(Bundle B) {
wallet = B.getString("Wallet");
if (B.containsKey("balanceReceived")) setBalanceReceived(B.getBoolean("balanceReceived"));
validationNodeDescription = B.getString("validationNodeDescription");
@ -40,6 +60,8 @@ public abstract class CoinData {
public void saveToBundle(Bundle B) {
try {
B.putString("Wallet", wallet);
if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual);
if (failedBalanceRequestCounter != null)

View file

@ -3,7 +3,7 @@ package com.tangem.domain.wallet;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.tangemcard.reader.CardProtocol;
import java.math.BigDecimal;
import java.math.BigInteger;

View file

@ -6,6 +6,7 @@ import com.tangem.domain.wallet.btc.BtcEngine
import com.tangem.domain.wallet.eth.EthEngine
import com.tangem.domain.wallet.token.TokenEngine
import com.tangem.domain.wallet.bch.BtcCashEngine
import com.tangem.tangemcard.data.Blockchain
/**
* Factory for create specific engine

View file

@ -5,6 +5,8 @@ import android.content.Intent;
import android.os.Bundle;
import com.tangem.Constant;
import com.tangem.tangemcard.data.Blockchain;
import com.tangem.tangemcard.data.TangemCard;
public class TangemContext {
@ -121,4 +123,17 @@ public class TangemContext {
if (context != null) return getContext().getResources().getString(stringId);
return "context.resources.string[" + stringId + "]";
}
public void setDenomination(byte[] denomination) {
try {
CoinEngine engine= CoinEngineFactory.INSTANCE.create(getBlockchain());
CoinEngine.InternalAmount internalAmount=engine.convertToInternalAmount(denomination);
CoinEngine.Amount amount=engine.convertToAmount(internalAmount);
card.setDenomination(denomination,amount.toString());
} catch (Exception e) {
e.printStackTrace();
card.setDenomination(denomination,"N/A");
}
}
}

View file

@ -3,16 +3,16 @@ package com.tangem.domain.wallet.bch;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.TLV;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.tangemcard.data.Blockchain;
import com.tangem.domain.wallet.CoinEngineFactory;
import com.tangem.domain.wallet.btc.BtcData;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.Transaction;
import com.tangem.domain.wallet.UnspentOutputInfo;
@ -20,7 +20,7 @@ import com.tangem.domain.wallet.BTCUtils;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.util.DerEncodingUtil;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import java.io.ByteArrayOutputStream;

View file

@ -3,15 +3,15 @@ package com.tangem.domain.wallet.btc;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.TLV;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Base58;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.tangemcard.data.Blockchain;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.Transaction;
import com.tangem.domain.wallet.UnspentOutputInfo;
@ -19,7 +19,7 @@ import com.tangem.domain.wallet.BTCUtils;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.util.DerEncodingUtil;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import java.io.ByteArrayOutputStream;

View file

@ -4,18 +4,18 @@ import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.TLV;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.tangemcard.data.Blockchain;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.ECDSASignatureETH;
import com.tangem.domain.wallet.EthTransaction;
import com.tangem.domain.wallet.Issuer;
import com.tangem.tangemcard.data.Issuer;
import com.tangem.domain.wallet.Keccak256;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.util.CryptoUtil;

View file

@ -5,17 +5,17 @@ import android.text.InputFilter;
import android.util.Log;
import com.google.common.base.Strings;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.TLV;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.ECDSASignatureETH;
import com.tangem.domain.wallet.EthTransaction;
import com.tangem.domain.wallet.Issuer;
import com.tangem.tangemcard.data.Issuer;
import com.tangem.domain.wallet.Keccak256;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.util.CryptoUtil;

View file

@ -18,13 +18,15 @@ import com.tangem.data.network.ServerApiCommon
import com.tangem.data.network.ServerApiElectrum
import com.tangem.data.network.ServerApiInfura
import com.tangem.data.network.model.InfuraResponse
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.domain.wallet.btc.BtcData
import com.tangem.tangemcard.data.Blockchain
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.util.Util
import com.tangem.util.*
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_confirm_payment.*
import org.json.JSONException
import java.io.IOException
import java.math.BigDecimal
import java.math.BigInteger

View file

@ -12,14 +12,13 @@ import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.data.nfc.CreateNewWalletTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.tasks.CreateNewWalletTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_create_new_wallet.*

View file

@ -13,14 +13,14 @@ import android.support.v7.app.AppCompatActivity
import android.text.Html
import android.view.View
import android.widget.Toast
import com.tangem.data.nfc.VerifyCardTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.tasks.VerifyCardTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.tangemcard.data.TangemCard
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_empty_wallet.*

View file

@ -26,18 +26,19 @@ import android.widget.Toast
import com.scottyab.rootbeer.RootBeer
import com.tangem.App
import com.tangem.data.Logger
import com.tangem.data.db.PINStorage
import com.tangem.tangemcard.data.PINStorage
import com.tangem.data.network.ServerApiCommon
import com.tangem.data.nfc.DeviceNFCAntennaLocation
import com.tangem.data.nfc.ReadCardInfoTask
import com.tangem.tangemcard.data.DeviceNFCAntennaLocation
import com.tangem.tangemcard.tasks.ReadCardInfoTask
import com.tangem.di.Navigator
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.Firmwares
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.data.Firmwares
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.RootFoundDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.tangemcard.data.Issuer
import com.tangem.tangemcard.data.TangemCard
import com.tangem.util.CommonUtil
import com.tangem.util.PhoneUtility
import com.tangem.wallet.BuildConfig

View file

@ -20,10 +20,10 @@ import android.util.Log
import android.view.View
import android.widget.Button
import com.tangem.data.fingerprint.StartFingerprintReaderTask
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.data.fingerprint.FingerprintHelper
import com.tangem.data.db.PINStorage
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.data.PINStorage
import com.tangem.tangemcard.data.TangemCard
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_request.*
import kotlinx.android.synthetic.main.layout_pin_buttons.*

View file

@ -21,7 +21,7 @@ import android.widget.Button
import android.widget.Toast
import com.tangem.data.fingerprint.ConfirmWithFingerprintTask
import com.tangem.data.fingerprint.FingerprintHelper
import com.tangem.data.db.PINStorage
import com.tangem.tangemcard.data.PINStorage
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_save.*
import kotlinx.android.synthetic.main.layout_pin_buttons.*

View file

@ -12,13 +12,13 @@ import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.data.nfc.SwapPINTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.tasks.SwapPINTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.tangemcard.data.TangemCard
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_swap.*

View file

@ -10,8 +10,8 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.tangem.data.network.CryptonitOtherApi
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.tangemcard.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.wallet.R

View file

@ -13,8 +13,8 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.tangem.data.network.Cryptonit
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.tangemcard.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.util.DecimalDigitsInputFilter

View file

@ -16,8 +16,8 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.tangem.data.network.Kraken
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.tangemcard.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.wallet.R

View file

@ -12,8 +12,8 @@ import android.text.Html
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.tangemcard.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.wallet.R

View file

@ -12,14 +12,13 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Toast
import com.tangem.data.nfc.PurgeTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.tasks.PurgeTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_purge.*

View file

@ -11,9 +11,11 @@ import com.tangem.data.network.ElectrumRequest
import com.tangem.data.network.ServerApiElectrum
import com.tangem.data.network.ServerApiInfura
import com.tangem.data.network.model.InfuraResponse
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.domain.wallet.eth.EthData
import com.tangem.tangemcard.data.Blockchain
import com.tangem.tangemcard.data.TangemCard
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import java.io.IOException

View file

@ -13,14 +13,14 @@ import android.view.KeyEvent
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.data.nfc.SignPaymentTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.tasks.SignPaymentTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_sign_payment.*

View file

@ -8,7 +8,7 @@ import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinData
import com.tangem.domain.wallet.TangemCard
import com.tangem.tangemcard.data.TangemCard
import com.tangem.presentation.fragment.VerifyCard
import com.tangem.wallet.R
import javax.inject.Inject

View file

@ -19,17 +19,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.tangem.data.db.LocalStorage
import com.tangem.data.db.PINStorage
import com.tangem.tangemcard.data.LocalStorage
import com.tangem.tangemcard.data.PINStorage
import com.tangem.data.network.ElectrumRequest
import com.tangem.data.network.ServerApiCommon
import com.tangem.data.network.ServerApiElectrum
import com.tangem.data.network.ServerApiInfura
import com.tangem.data.network.model.CardVerifyAndGetInfo
import com.tangem.data.network.model.InfuraResponse
import com.tangem.data.nfc.VerifyCardTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.tasks.VerifyCardTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.domain.wallet.bch.BtcCashEngine
import com.tangem.domain.wallet.btc.BtcData
@ -41,7 +41,9 @@ import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.PINSwapWarningDialog
import com.tangem.presentation.dialog.ShowQRCodeDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.data.Blockchain
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.util.Util
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fr_loaded_wallet.*

View file

@ -13,14 +13,16 @@ import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.Toast
import com.tangem.data.db.PINStorage
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.data.PINStorage
import com.tangem.tangemcard.reader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.presentation.activity.CreateNewWalletActivity
import com.tangem.presentation.activity.PurgeActivity
import com.tangem.presentation.activity.PinRequestActivity
import com.tangem.presentation.activity.PinSwapActivity
import com.tangem.presentation.dialog.PINSwapWarningDialog
import com.tangem.tangemcard.data.Blockchain
import com.tangem.tangemcard.data.TangemCard
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fr_verify_card.*

View file

@ -3,6 +3,7 @@ package com.tangem.util;
import android.util.Log;
import com.tangem.domain.wallet.ECDSASignatureETH;
import com.tangem.tangemcard.util.Util;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;

View file

@ -4,10 +4,8 @@ apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
@ -22,6 +20,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildToolsVersion '28.0.3'
}
@ -29,10 +28,37 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
// implementation 'com.android.support:design:28.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.madgag.spongycastle:core:1.56.0.0'
implementation 'com.madgag.spongycastle:prov:1.56.0.0'
implementation 'com.scottyab:rootbeer-lib:0.0.7'
// implementation 'com.android.support:support-compat:28.0.0'
// implementation 'com.android.support:cardview-v7:28.0.0'
// implementation 'com.android.support:support-v4:28.0.0'
// implementation 'com.android.support.constraint:constraint-layout:1.1.3'
// implementation 'com.google.zxing:core:3.3.3'
// implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
// implementation 'com.squareup.retrofit2:retrofit:2.5.0'
// implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
// implementation 'com.skyfishjy.ripplebackground:library:1.0.1'
// implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
// implementation 'com.google.dagger:dagger:2.16'
implementation 'org.bitcoinj:bitcoinj-parent:0.14.7'
implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
// implementation 'me.dm7.barcodescanner:zxing:1.9.8'
// implementation 'info.hoang8f:android-segmented:1.0.6'
// implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
// implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
// implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()

View file

@ -1,2 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tangem.tangemcard" />
package="com.tangem.tangemcard" >
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<!--<provider-->
<!--android:name="com.tangem.data.LogFileProvider"-->
<!--android:authorities="@string/log_file_provider_authorities"-->
<!--android:enabled="true"-->
<!--android:exported="false"-->
<!--android:grantUriPermissions="true" />-->
</application>
</manifest>

View file

@ -1,7 +1,6 @@
package com.tangem.domain.wallet;
package com.tangem.tangemcard.data;
import com.google.common.base.Strings;
import com.tangem.wallet.R;
import com.tangem.tangemcard.R;
/**
* Created by dvol on 06.08.2017.
@ -71,7 +70,7 @@ public enum Blockchain {
}
public int getImageResource(android.content.Context context, String name) {
if (Strings.isNullOrEmpty(name))
if (name==null || name.isEmpty())
return getImageResource();
name = name.toLowerCase();

View file

@ -1,7 +1,6 @@
package com.tangem.data.nfc
package com.tangem.tangemcard.data
import android.os.Build
import com.tangem.tangemcard.nfc.NFCLocation
class DeviceNFCAntennaLocation {
companion object {

View file

@ -1,11 +1,11 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.data;
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 com.tangem.tangemcard.util.Util;
import java.io.IOException;
import java.io.InputStream;

View file

@ -1,15 +1,12 @@
package com.tangem.domain.wallet;
package com.tangem.tangemcard.data;
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.tangemcard.reader.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;

View file

@ -1,4 +1,4 @@
package com.tangem.data.db
package com.tangem.tangemcard.data
import android.annotation.SuppressLint
import android.content.Context
@ -11,7 +11,7 @@ import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.tangem.data.network.model.CardVerifyAndGetInfo
import com.tangem.domain.cardReader.CardCrypto
import com.tangem.tangemcard.reader.CardCrypto
import com.tangem.domain.wallet.TangemCard
import com.tangem.util.Util
import com.tangem.wallet.R

View file

@ -1,4 +1,4 @@
package com.tangem.domain.wallet;
package com.tangem.tangemcard.data;
/**
* Created by dvol on 09.08.2017.

View file

@ -1,4 +1,4 @@
package com.tangem.tangemcard.nfc
package com.tangem.tangemcard.data
enum class NFCLocation(val codename: String, val fullName: String, val orientation: Int, val x: Int, val y: Int, val z: Int) {
model1("sailfish", "Google Pixel", 0, 65, 25, 0),

View file

@ -1,11 +1,11 @@
package com.tangem.data.db;
package com.tangem.tangemcard.data;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.tangemcard.reader.CardProtocol;
import java.util.ArrayList;
import java.util.List;

View file

@ -1,12 +1,11 @@
package com.tangem.domain.wallet;
package com.tangem.tangemcard.data;
import android.os.Bundle;
import android.util.Log;
import com.google.common.base.Strings;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.SettingsMask;
import com.tangem.util.Util;
import com.tangem.tangemcard.reader.SettingsMask;
import com.tangem.tangemcard.util.Util;
import java.util.Date;
@ -21,7 +20,6 @@ public class TangemCard {
private byte[] CID;
private Status status;
private String wallet;
private String UID;
private String blockchainID;
private Manufacturer manufacturer = Manufacturer.Unknown;
@ -43,7 +41,7 @@ public class TangemCard {
}
public void setBlockchain(Blockchain blockchain) {
blockchainID=blockchain.getID();
blockchainID = blockchain.getID();
}
public void setBlockchainID(String blockchainID) {
@ -57,7 +55,7 @@ public class TangemCard {
String oldName = getBlockchain().getOfficialName();
//String newName = String.format("%s - %s ERC20 token", token, oldName);
String newName = token + " <br><small><small> " + oldName + " ERC20 token</small></small>";
blockchainName=newName;
blockchainName = newName;
}
@ -549,22 +547,6 @@ public class TangemCard {
}
}
public void setWallet(String wallet) {
this.wallet = wallet;
}
public String getWallet() {
return wallet;
}
public String getShortWalletString() {
if (wallet.length() < 22) {
return wallet;
} else {
return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length());
}
}
public String getUID() {
return UID;
}
@ -596,19 +578,6 @@ public class TangemCard {
this.DenominationText = denominationText;
}
public void setDenomination(byte[] denomination) {
this.Denomination = denomination;
try {
CoinEngine engine= CoinEngineFactory.INSTANCE.create(getBlockchain());
CoinEngine.InternalAmount internalAmount=engine.convertToInternalAmount(denomination);
CoinEngine.Amount amount=engine.convertToAmount(internalAmount);
this.DenominationText = amount.toString();
} catch (Exception e) {
e.printStackTrace();
this.DenominationText = "N/A";
}
}
public byte[] getDenomination() {
return Denomination;
}
@ -673,7 +642,6 @@ public class TangemCard {
B.putByteArray("CardPublicKey", getCardPublicKey());
B.putInt("SignedHashes", getSignedHashes());
B.putString("Wallet", wallet);
B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid());
if (pbWalletKey != null)
B.putByteArray("PublicKey", pbWalletKey);
@ -683,6 +651,7 @@ public class TangemCard {
if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance());
if (getDenomination() != null) B.putByteArray("Denomination", getDenomination());
if (getDenominationText() != null) B.putString("DenominationText", getDenominationText());
if (getIssuerData() != null && getIssuerDataSignature() != null) {
B.putByteArray("IssuerData", getIssuerData());
@ -725,7 +694,6 @@ public class TangemCard {
PIN = B.getString("PIN");
PIN2 = PIN2_Mode.valueOf(B.getString("PIN2"));
status = Status.valueOf(B.getString("Status"));
wallet = B.getString("Wallet");
blockchainID = B.getString("Blockchain");
tokensDecimal = B.getInt("TokensDecimal", 18);
tokenSymbol = B.getString("TokenSymbol", "");
@ -765,8 +733,11 @@ public class TangemCard {
if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance"));
else clearOfflineBalance();
if (B.containsKey("Denomination")) setDenomination(B.getByteArray("Denomination"));
else clearDenomination();
if (B.containsKey("Denomination") && B.containsKey("DenominationText")) {
setDenomination(B.getByteArray("Denomination"), B.getString("DenominationText"));
} else if (B.containsKey("Denomination")) {
setDenomination(B.getByteArray("Denomination"), "N/A");
} else clearDenomination();
if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature"))
setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"));

View file

@ -1,8 +1,8 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import android.util.Log;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
@ -23,7 +23,6 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import android.content.Context;
import android.nfc.Tag;
@ -6,14 +6,12 @@ import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.CoinEngineFactory;
import com.tangem.domain.wallet.Issuer;
import com.tangem.data.db.LocalStorage;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.Manufacturer;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.util.Util;
import com.tangem.tangemcard.data.Issuer;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.data.Firmwares;
import com.tangem.tangemcard.data.LocalStorage;
import com.tangem.tangemcard.data.Manufacturer;
import com.tangem.tangemcard.util.Util;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.interfaces.ECPublicKey;
@ -29,6 +27,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.Calendar;
import java.util.TreeMap;
import javax.crypto.KeyAgreement;
@ -153,34 +152,25 @@ public class CardProtocol {
}
/**
* Constructor for reading a card for the first time
* Constructor
*
* @param context - Android context
* @param isoDep - Android NFC tag
* @param notifications - UI notification callbacks
*/
public CardProtocol(Context context, IsoDep isoDep, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mPIN = null;
mCard = new TangemCard(Util.byteArrayToHexString(mIsoDep.getTag().getId()));
}
/**
* Constructor for subsequent reading of the card
*
* @param context - Android context
* @param isoDep - Android NFC tag
* @param card - TangemCard object, stored data from previous reading
* @param card - TangemCard object, stored data from previous reading or null for reading a card for the first time
* @param notifications - UI notification callbacks
*/
public CardProtocol(Context context, IsoDep isoDep, TangemCard card, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mPIN = card.getPIN();
mCard = card;
if (card != null)
{
mPIN = card.getPIN();
mCard = card;
}else{
mPIN = null;
mCard = new TangemCard(Util.byteArrayToHexString(mIsoDep.getTag().getId()));
}
}
/**
@ -502,15 +492,15 @@ public class CardProtocol {
return Apdu;
}
/**
* Run READ command and parse answer
* {@see run_Read(boolean parseResult) }
*
* @throws Exception if something went wrong
*/
public void run_Read() throws Exception {
run_Read(true);
}
// /**
// * Run READ command and parse answer
// * {@see run_Read(boolean parseResult) }
// *
// * @throws Exception if something went wrong
// */
// public void run_Read() throws Exception {
// run_Read1(true);
// }
/**
* Run READ command and parse answer (if specified)
@ -523,10 +513,9 @@ public class CardProtocol {
* This command requires only PIN1 parameter while other commands also need CID
* See [1] 8, 8.2
*
* @param parseResult - parse answer into TangemCard object or not
* @throws Exception if something went wrong
*/
public void run_Read(boolean parseResult) throws Exception {
public void run_Read1() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.Read);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -536,15 +525,6 @@ public class CardProtocol {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
readResult = rspApdu.getTLVs();
if (parseResult) {
// After first successful read, data will be parsed into TangemCard object
parseReadResult();
//TODO: move issuer data out from here
run_ReadWriteIssuerData();
} else {
//TODO: check that card is the same
}
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible PIN is invalid!\n", rspApdu.getSW1SW2()));
} else {
@ -553,54 +533,6 @@ public class CardProtocol {
}
/**
* Executing GET_ISSUER_DATA or WRITE_ISSUER_DATA depending on state in TangemCard object {@see TangemCard.getNeedWriteIssuerData()}
* See [1] 8.7
*
* @throws Exception if something went wrong
*/
public void run_ReadWriteIssuerData() throws Exception {
TLVList tlvIssuerData;
if (mCard.getNeedWriteIssuerData()) {
run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature());
try {
tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData());
} catch (TLVException e) {
e.printStackTrace();
tlvIssuerData = null;
}
} else {
try {
tlvIssuerData = run_GetIssuerData();
} catch (Exception e) {
e.printStackTrace();
tlvIssuerData = null;
}
}
// try read offline balance data
if (mCard.getStatus() == TangemCard.Status.Loaded) {
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_ValidatedBalance) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) {
mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_ValidatedBalance).Value);
} else {
mCard.clearOfflineBalance();
}
} else {
mCard.clearOfflineBalance();
}
// try read denomination
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null) {
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();
}
}
/**
* A TLV list containing response of the last READ command
*/
@ -615,181 +547,12 @@ public class CardProtocol {
return readResult != null;
}
/**
* Parse response of the last READ command into TangemCard object
* See [1] 8.2, 5.3, 3.3
*
* @throws TangemException - if something went wrong
*/
public void parseReadResult() throws TangemException {
// These tags always present in the parsed response: TAG_Status, TAG_CID, TAG_Manufacture_ID, TAG_Health, TAG_Firmware
TLV tlvStatus = readResult.getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(TangemCard.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
TLV tlvCID = readResult.getTLV(TLV.Tag.TAG_CardID);
mCard.setCID(tlvCID.Value);
mCard.setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
// Support of legacy Firmware
if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) {
mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
mCard.setHealth(readResult.getTLV(TLV.Tag.TAG_Health).getAsInt());
// If the card was previously personalized then parse personalized card data - card public key, blockchain data and other settings
if (mCard.getStatus() != TangemCard.Status.NotPersonalized) {
try {
TLV tlvCardPubkicKey = readResult.getTLV(TLV.Tag.TAG_CardPublicKey);
if (tlvCardPubkicKey == null)
throw new TangemException("Invalid answer format");
mCard.setCardPublicKey(tlvCardPubkicKey.Value);
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);
if (tokenSymbol != null)
mCard.setTokenSymbol(tokenSymbol.getAsString());
if (contractAddress != null)
mCard.setContractAddress(contractAddress.getAsString());
if (tokens_decimal != null)
mCard.setTokensDecimal(tokens_decimal.getAsInt());
byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value;
int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF);
int month = tlvPersonalizationDT[2] - 1;
int day = tlvPersonalizationDT[3];
Calendar cd = Calendar.getInstance();
cd.set(year, month, day, 0, 0, 0);
mCard.setPersonalizationDateTime(cd.getTime());
try {
// Support of legacy Firmware
if (mCard.getFirmwareVersion() == null) {
mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Cannot get firmware version");
mCard.setFirmwareVersion("0.00");
}
try {
if (mCard.getFirmwareVersion().compareTo("1.05") < 0) {
// In FW ver 1.05, the issuer has one key pair used as both DataKey and TransactionKey, see [1] 3.3.1 and [1] 3.3.2
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} else {
// In newer FW versions, the issuer has two different key pairs - DataKey and TransactionKey, see [1] 3.3.1 and [1] 3.3.2
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value);
}
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Cannot get issuer, try a version for older cards");
try {
// for very very old cards Issuer key can be stored in different TLV tag
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} catch (Exception ee) {
ee.printStackTrace();
Log.e(logTag, "Cannot get issuer");
mCard.setIssuer(Issuer.Unknown().getID(), null);
}
}
// Overriding of missing card data, e.g. for cards with unknown ERC20 contract data
// This method must be called after the issuer data is defined because newly written data is verified by issuer data key
try {
// Hardcoded for some known batches, or, for other batches, received from Tangem server and stored in local storage
if (mCard.getBatch().equals("0017")) {
mCard.setContractAddress("0x9Eef75bA8e81340da9D8d1fd06B2f313DB88839c");
} else if (mCard.getBatch().equals("0019")) {
mCard.setContractAddress("0x0c056b0cda0763cc14b8b2d6c02465c91e33ec72");
} else {
LocalStorage localStorage = new LocalStorage(mContext);
localStorage.applySubstitution(mCard);
}
} catch (Exception e) {
Log.e(logTag, "Can't apply card data substitution");
e.printStackTrace();
}
mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString());
try {
mCard.setSettingsMask(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask));
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Can't get settings mask");
}
if (readResult.getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) {
mCard.setPauseBeforePIN2(10 * readResult.getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2));
}
try {
mCard.setSigningMethod(readResult.getTagAsInt(TLV.Tag.TAG_SigningMethod));
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Can't get signing method");
mCard.setSigningMethod(0);
}
try {
mCard.setMaxSignatures(readResult.getTagAsInt(TLV.Tag.TAG_MaxSignatures));
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Can't get max signatures");
}
} catch (Exception e) {
e.printStackTrace();
throw new TangemException("Can't parse card data");
}
}
// Parse additional parameters for Loaded cards: wallet public key, remaining signatures, etc
if (mCard.getStatus() == TangemCard.Status.Loaded) {
TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value);
byte pkUncompressed[] = p1.getEncoded(false);
byte pkCompresses[] = p1.getEncoded(true);
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
TangemContext ctx = new TangemContext(mCard);
try {
// TODO - store in mCard only public key, not wallet address, move wallet address to coinData
CoinEngine engineCoin = CoinEngineFactory.INSTANCE.create(ctx);
if (engineCoin == null) throw new Exception("Can't create CoinEngine!");
String wallet = engineCoin.calculateAddress(pkUncompressed);
mCard.setWallet(wallet);
} catch (Exception e) {
e.printStackTrace();
throw new TangemException("Can't define wallet address");
}
//mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed));
mCard.setRemainingSignatures(readResult.getTagAsInt(TLV.Tag.TAG_RemainingSignatures));
if (readResult != null && readResult.getTLV(TLV.Tag.TAG_SignedHashes) != null) {
mCard.setSignedHashes(readResult.getTagAsInt(TLV.Tag.TAG_SignedHashes));
}
} else {
mCard.setWallet("N/A");
}
public TLVList getReadResult()
{
return readResult;
}
/**
* VERIFY_CARD command and verify card's signature
* By using standard challenge-response scheme, the card proves
@ -801,7 +564,7 @@ public class CardProtocol {
*/
public TLVList run_VerifyCard() throws Exception {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
throw new TangemException("Before run_VerifyCard execute run_Read card first!");
}
if (mCard.getStatus() == TangemCard.Status.NotPersonalized) {
getCard().setManufacturer(Manufacturer.Unknown, false);
@ -869,7 +632,7 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
public void run_CreateWallet(String PIN2) throws Exception {
if (readResult == null) run_Read();
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -930,7 +693,7 @@ public class CardProtocol {
*/
public void run_CheckWalletWithSignatureVerify() throws Exception {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
throw new TangemException("Before run_VerifyCard execute run_Read card first!");
}
if (mCard.getStatus() == TangemCard.Status.NotPersonalized) {
getCard().setManufacturer(Manufacturer.Unknown, false);
@ -1043,29 +806,6 @@ public class CardProtocol {
}
}
/**
* Verify if default PIN2 is set on the card and enable UseDefaultPIN2 flag {@link TangemCard}
* Works in firmware 1.12 and later, for older version UseDefaultPIN2 is always null
* This function NEVER triggers security delay
*
* @throws Exception - if something went wrong
*/
public void run_CheckPIN2isDefault() throws Exception {
// can obtain SetPIN(to default) answer without security delay - try check if PIN2 is default with card request
if (mCard.isFirmwareNewer("1.19") || (mCard.isFirmwareNewer("1.12") && (mCard.getPauseBeforePIN2() == 0 || mCard.useSmartSecurityDelay()))) {
try {
run_SetPIN(DefaultPIN2, mPIN, DefaultPIN2, true);
mCard.setUseDefaultPIN2(true);
} catch (TangemException_NeedPause e) {
mCard.setUseDefaultPIN2(null);
} catch (TangemException_InvalidPIN e) {
mCard.setUseDefaultPIN2(false);
}
} else {
mCard.setUseDefaultPIN2(null);
}
}
/**
* SIGN command to sign hashes - SigningMethod=0,2,4 (see {@link TangemCard.SigningMethod})
* See [1] 8.6
@ -1177,7 +917,7 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
public byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception {
if (readResult == null) run_Read();
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode);
rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII"));
rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress);
@ -1212,7 +952,7 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
private void run_ValidateCard(String PIN2) throws Exception {
if (readResult == null) run_Read();
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
@ -1244,8 +984,8 @@ public class CardProtocol {
* @param issuerSignature - signature of issuerData with IssuerDataKey
* @throws Exception - if something went wrong
*/
private void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception {
run_Read();
public void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception {
if( readResult==null ) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
@ -1269,7 +1009,7 @@ public class CardProtocol {
* @return TLVList with issuerData (if success read and verify)
* @throws Exception - if something went wrong
*/
private TLVList run_GetIssuerData() throws Exception {
public TLVList run_GetIssuerData() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -1342,4 +1082,5 @@ public class CardProtocol {
} while (true);
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
/**
* Created by dvol on 07.03.2018.

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import android.Manifest;
import android.annotation.TargetApi;

View file

@ -1,5 +1,5 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.digests.SHA256Digest;

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import com.tangem.util.Util;

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
/**
* Created by dvol on 07.03.2018.

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
/**
* Created by dvol on 07.03.2018.

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
import com.tangem.util.Util;

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
public class TLVException extends Exception {

View file

@ -1,4 +1,4 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.reader;
/**
* Created by dvol on 23.06.2017.

View file

@ -1,13 +1,13 @@
package com.tangem.data.nfc;
package com.tangem.tangemcard.tasks;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcManager;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.data.TangemCard;
public class CreateNewWalletTask extends Thread {
public static final String TAG = CreateNewWalletTask.class.getSimpleName();

View file

@ -0,0 +1,454 @@
package com.tangem.tangemcard.tasks;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.tangemcard.data.Issuer;
import com.tangem.tangemcard.data.LocalStorage;
import com.tangem.tangemcard.data.Manufacturer;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcManager;
import com.tangem.tangemcard.reader.TLV;
import com.tangem.tangemcard.reader.TLVException;
import com.tangem.tangemcard.reader.TLVList;
import com.tangem.tangemcard.util.Util;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.math.ec.ECPoint;
import java.util.ArrayList;
import java.util.Calendar;
/**
* Base class for card communication task
*/
public class CustomReadCardTask extends Thread {
public static final String TAG = CustomReadCardTask.class.getSimpleName();
protected IsoDep mIsoDep;
protected CardProtocol.Notifications mNotifications;
protected boolean isCancelled = false;
protected Context mContext;
protected NfcManager mNfcManager;
protected TangemCard mCard ;
CardProtocol protocol;
// 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 static void resetLastReadInfo() {
lastRead_UID = "";
lastRead_Encryption = null;
lastRead_UnsuccessfullPINs.clear();
}
public CustomReadCardTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mNfcManager = nfcManager;
mCard=card;
}
/**
* Executing GET_ISSUER_DATA or WRITE_ISSUER_DATA depending on state in TangemCard object {@see TangemCard.getNeedWriteIssuerData()}
* See [1] 8.7
* Usually this function must be called if run_Task() of descendants
*
* @throws Exception if something went wrong
*/
protected void run_ReadOrWriteIssuerData() throws Exception {
TLVList tlvIssuerData;
if (mCard.getNeedWriteIssuerData()) {
protocol.run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature());
try {
tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData());
} catch (TLVException e) {
e.printStackTrace();
tlvIssuerData = null;
}
} else {
try {
tlvIssuerData = protocol.run_GetIssuerData();
} catch (Exception e) {
e.printStackTrace();
tlvIssuerData = null;
}
}
// try read offline balance data
if (mCard.getStatus() == TangemCard.Status.Loaded) {
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_ValidatedBalance) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) {
mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_ValidatedBalance).Value);
} else {
mCard.clearOfflineBalance();
}
} else {
mCard.clearOfflineBalance();
}
// try read denomination
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null) {
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, null);
}
} else {
mCard.clearDenomination();
}
}
/**
* Parse response of the last READ command into TangemCard object
* See [1] 8.2, 5.3, 3.3
*
* @throws CardProtocol.TangemException - if something went wrong
*/
public void parseReadResult() throws CardProtocol.TangemException {
// These tags always present in the parsed response: TAG_Status, TAG_CID, TAG_Manufacture_ID, TAG_Health, TAG_Firmware
TLV tlvStatus = protocol.getReadResult().getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(TangemCard.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
TLV tlvCID = protocol.getReadResult().getTLV(TLV.Tag.TAG_CardID);
mCard.setCID(tlvCID.Value);
mCard.setManufacturer(Manufacturer.FindManufacturer(protocol.getReadResult().getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
// Support of legacy Firmware
if (protocol.getReadResult().getTLV(TLV.Tag.TAG_Firmware) != null) {
mCard.setFirmwareVersion(protocol.getReadResult().getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
mCard.setHealth(protocol.getReadResult().getTLV(TLV.Tag.TAG_Health).getAsInt());
// If the card was previously personalized then parse personalized card data - card public key, blockchain data and other settings
if (mCard.getStatus() != TangemCard.Status.NotPersonalized) {
try {
TLV tlvCardPubkicKey = protocol.getReadResult().getTLV(TLV.Tag.TAG_CardPublicKey);
if (tlvCardPubkicKey == null)
throw new CardProtocol.TangemException("Invalid answer format");
mCard.setCardPublicKey(tlvCardPubkicKey.Value);
TLVList tlvCardData = TLVList.fromBytes(protocol.getReadResult().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);
if (tokenSymbol != null)
mCard.setTokenSymbol(tokenSymbol.getAsString());
if (contractAddress != null)
mCard.setContractAddress(contractAddress.getAsString());
if (tokens_decimal != null)
mCard.setTokensDecimal(tokens_decimal.getAsInt());
byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value;
int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF);
int month = tlvPersonalizationDT[2] - 1;
int day = tlvPersonalizationDT[3];
Calendar cd = Calendar.getInstance();
cd.set(year, month, day, 0, 0, 0);
mCard.setPersonalizationDateTime(cd.getTime());
try {
// Support of legacy Firmware
if (mCard.getFirmwareVersion() == null) {
mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Cannot get firmware version");
mCard.setFirmwareVersion("0.00");
}
try {
if (mCard.getFirmwareVersion().compareTo("1.05") < 0) {
// In FW ver 1.05, the issuer has one key pair used as both DataKey and TransactionKey, see [1] 3.3.1 and [1] 3.3.2
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} else {
// In newer FW versions, the issuer has two different key pairs - DataKey and TransactionKey, see [1] 3.3.1 and [1] 3.3.2
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value);
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Cannot get issuer, try a version for older cards");
try {
// for very very old cards Issuer key can be stored in different TLV tag
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} catch (Exception ee) {
ee.printStackTrace();
Log.e(TAG, "Cannot get issuer");
mCard.setIssuer(Issuer.Unknown().getID(), null);
}
}
// Overriding of missing card data, e.g. for cards with unknown ERC20 contract data
// This method must be called after the issuer data is defined because newly written data is verified by issuer data key
try {
// Hardcoded for some known batches, or, for other batches, received from Tangem server and stored in local storage
if (mCard.getBatch().equals("0017")) {
mCard.setContractAddress("0x9Eef75bA8e81340da9D8d1fd06B2f313DB88839c");
} else if (mCard.getBatch().equals("0019")) {
mCard.setContractAddress("0x0c056b0cda0763cc14b8b2d6c02465c91e33ec72");
} else {
LocalStorage localStorage = new LocalStorage(mContext);
localStorage.applySubstitution(mCard);
}
} catch (Exception e) {
Log.e(TAG, "Can't apply card data substitution");
e.printStackTrace();
}
mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString());
try {
mCard.setSettingsMask(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_SettingsMask));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't get settings mask");
}
if (protocol.getReadResult().getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) {
mCard.setPauseBeforePIN2(10 * protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2));
}
try {
mCard.setSigningMethod(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_SigningMethod));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't get signing method");
mCard.setSigningMethod(0);
}
try {
mCard.setMaxSignatures(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_MaxSignatures));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't get max signatures");
}
} catch (Exception e) {
e.printStackTrace();
throw new CardProtocol.TangemException("Can't parse card data");
}
}
// Parse additional parameters for Loaded cards: wallet public key, remaining signatures, etc
if (mCard.getStatus() == TangemCard.Status.Loaded) {
TLV tlvPublicKey = protocol.getReadResult().getTLV(TLV.Tag.TAG_Wallet_PublicKey);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value);
byte pkUncompressed[] = p1.getEncoded(false);
byte pkCompresses[] = p1.getEncoded(true);
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
// TODO - store in mCard only public key, not wallet address, move wallet address to coinData
// TangemContext ctx = new TangemContext(mCard);
// try {
// CoinEngine engineCoin = CoinEngineFactory.INSTANCE.create(ctx);
// if (engineCoin == null) throw new Exception("Can't create CoinEngine!");
// String wallet = engineCoin.calculateAddress(pkUncompressed);
// mCard.setWallet(wallet);
// } catch (Exception e) {
// e.printStackTrace();
// throw new TangemException("Can't define wallet address");
// }
//mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed));
mCard.setRemainingSignatures(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_RemainingSignatures));
if (protocol.getReadResult() != null && protocol.getReadResult().getTLV(TLV.Tag.TAG_SignedHashes) != null) {
mCard.setSignedHashes(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_SignedHashes));
}
} else {
// mCard.setWallet("N/A");
}
}
/**
* On first time card reading run READ command, parse answer and Executing GET_ISSUER_DATA or WRITE_ISSUER_DATA depending on state in TangemCard object {@see TangemCard.getNeedWriteIssuerData()}
* See [1] 8.7
*
* @throws Exception if something went wrong
*/
public void run_FirstTimeRead() throws Exception {
byte[] UID = mIsoDep.getTag().getId();
String sUID = Util.byteArrayToHexString(UID);
if (!lastRead_UID.equals(sUID)) {
resetLastReadInfo();
}
Log.i(TAG, "[-- Start read card info --]");
if (isCancelled) return;
protocol.setPIN(PINStorage.getDefaultPIN());
protocol.clearReadResult();
if (lastRead_Encryption == null) {
Log.i(TAG, "Try get supported encryption mode");
protocol.run_GetSupportedEncryption();
} else {
Log.i(TAG, "Use already defined encryption mode: " + lastRead_Encryption.name());
protocol.getCard().encryptionMode = lastRead_Encryption;
}
if (protocol.haveReadResult()) {
//already have read result (obtained while get supported encryption), only read issuer data and define offline balance
parseReadResult();
PINStorage.setLastUsedPIN(protocol.getCard().getPIN());
} else {
//don't have read result - may be don't get supported encryption on this try, need encryption or need another PIN
if (lastRead_Encryption == null) {
// we try get supported encryption on this time
lastRead_Encryption = protocol.getCard().encryptionMode;
if (protocol.getCard().encryptionMode == TangemCard.EncryptionMode.None) {
// default pin not accepted
lastRead_UnsuccessfullPINs.add(PINStorage.getDefaultPIN());
}
}
boolean pinFound = false;
for (String PIN : PINStorage.getPINs()) {
Log.e(TAG, "PIN: " + PIN);
boolean skipPin = false;
for (int i = 0; i < lastRead_UnsuccessfullPINs.size(); i++) {
if (lastRead_UnsuccessfullPINs.get(i).equals(PIN)) {
skipPin = true;
break;
}
}
if (skipPin) {
Log.e(TAG, "Skip PIN - already checked before");
continue;
}
try {
protocol.setPIN(PIN);
if (protocol.getCard().encryptionMode != TangemCard.EncryptionMode.None) {
protocol.CreateProtocolKey();
}
protocol.run_Read1();
// After first successful read, data will be parsed into TangemCard object
parseReadResult();
PINStorage.setLastUsedPIN(PIN);
pinFound = true;
protocol.getCard().setPIN(PIN);
break;
} catch (CardProtocol.TangemException_InvalidPIN e) {
Log.e(TAG, e.getMessage());
lastRead_UnsuccessfullPINs.add(PIN);
}
}
if (!pinFound) {
throw new CardProtocol.TangemException_InvalidPIN("No valid PIN found!");
}
}
}
/**
* Run read and check that the card is the same
*
* @throws Exception if something went wrong
*/
public void run_SecondTimeRead() throws Exception {
String PIN = mCard.getPIN();
protocol.setPIN(PIN);
protocol.run_Read1();
PINStorage.setLastUsedPIN(PIN);
if (!mCard.getCID().equals(protocol.getReadResult().getTLV(TLV.Tag.TAG_CardID))) {
throw new CardProtocol.TangemException("Card must be the same. Reading attempt on different card!");
}
}
/**
* This function must be override in descendants to arrive the task goal - sign, verify and etc
* @throws Exception if something went wrong
*/
public void run_Task() throws Exception {
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
Log.i(TAG, String.format("[-- Start task -- %s --]", getClass().getSimpleName()));
mNotifications.onReadProgress(protocol, 5);
if (mCard == null) {
// first time reading
run_FirstTimeRead();
} else {
run_SecondTimeRead();
}
run_Task();
mNotifications.onReadProgress(protocol, 100);
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, String.format("[-- Finish task -- %s --]", getClass().getSimpleName()));
mNotifications.onReadFinish(protocol);
}
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
mNfcManager.notifyReadResult(false);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,29 @@
package com.tangem.tangemcard.tasks;
import android.content.Context;
import android.nfc.tech.IsoDep;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcManager;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.data.TangemCard;
public class PurgeTask extends CustomReadCardTask {
public static final String TAG = PurgeTask.class.getSimpleName();
public PurgeTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
super(context, card, nfcManager, isoDep, notifications);
}
@Override
public void run_Task() throws Exception {
if (mCard.getPauseBeforePIN2() > 0) {
mNotifications.onReadWait(mCard.getPauseBeforePIN2());
}
protocol.run_PurgeWallet(PINStorage.getPIN2());
mNotifications.onReadProgress(protocol, 50);
protocol.run_Read1();
mNotifications.onReadProgress(protocol, 100);
}
}

View file

@ -0,0 +1,50 @@
package com.tangem.tangemcard.tasks;
import android.content.Context;
import android.nfc.tech.IsoDep;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcManager;
public class ReadCardInfoTask extends CustomReadCardTask {
public static final String TAG = ReadCardInfoTask.class.getSimpleName();
public ReadCardInfoTask(Context context, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
super(context, null, nfcManager, isoDep, notifications);
}
/**
* Verify if default PIN2 is set on the card and enable UseDefaultPIN2 flag {@link TangemCard}
* Works in firmware 1.12 and later, for older version UseDefaultPIN2 is always null
* This function NEVER triggers security delay
*
* @throws Exception - if something went wrong
*/
public void run_CheckPIN2isDefault() throws Exception {
// can obtain SetPIN(to default) answer without security delay - try check if PIN2 is default with card request
if (mCard.isFirmwareNewer("1.19") || (mCard.isFirmwareNewer("1.12") && (mCard.getPauseBeforePIN2() == 0 || mCard.useSmartSecurityDelay()))) {
try {
protocol.run_SetPIN(protocol.DefaultPIN2, mCard.getPIN(), protocol.DefaultPIN2, true);
mCard.setUseDefaultPIN2(true);
} catch (CardProtocol.TangemException_NeedPause e) {
mCard.setUseDefaultPIN2(null);
} catch (CardProtocol.TangemException_InvalidPIN e) {
mCard.setUseDefaultPIN2(false);
}
} else {
mCard.setUseDefaultPIN2(null);
}
}
@Override
public void run_Task() throws Exception {
mNotifications.onReadProgress(protocol, 20);
run_ReadOrWriteIssuerData();
mNotifications.onReadProgress(protocol, 50);
run_CheckPIN2isDefault();
mNotifications.onReadProgress(protocol, 90);
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.data.nfc;
package com.tangem.tangemcard.tasks;
import android.app.Activity;
import android.content.Intent;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcManager;
import com.tangem.tangemcard.data.Blockchain;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.CoinEngineFactory;
import com.tangem.domain.wallet.TangemContext;

View file

@ -1,13 +1,13 @@
package com.tangem.data.nfc;
package com.tangem.tangemcard.tasks;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcManager;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.data.TangemCard;
public class SwapPINTask extends Thread {
public static final String TAG = SwapPINTask.class.getSimpleName();

View file

@ -0,0 +1,51 @@
package com.tangem.tangemcard.tasks;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.data.Firmwares;
import com.tangem.tangemcard.reader.NfcManager;
import com.tangem.tangemcard.data.PINStorage;
import com.tangem.tangemcard.data.TangemCard;
import java.util.Arrays;
/**
* Created by dvol on 04.02.2018.
*/
public class VerifyCardTask extends CustomReadCardTask {
public static final String TAG = VerifyCardTask.class.getSimpleName();
public VerifyCardTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
super(context, card, nfcManager, isoDep, notifications);
}
@Override
public void run_Task() throws Exception {
mNotifications.onReadProgress(protocol, 20);
if (isCancelled) return;
protocol.run_VerifyCard();
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, 80);
}
if (isCancelled) return;
Firmwares.VerifyCodeRecord record=Firmwares.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);
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.util;
package com.tangem.tangemcard.util;
import android.text.format.DateUtils;

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After