Updated on 2026-08-14

This commit is contained in:
Tangem 2018-05-28 13:56:12 +03:00
commit 5e7300987c
243 changed files with 28116 additions and 0 deletions

View file

@ -0,0 +1,519 @@
package com.tangem.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
import android.util.Log;
import com.tangem.cardReader.Util;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.asn1.DERSequenceGenerator;
import org.spongycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.asn1.x9.X9IntegerConverter;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.jce.spec.ECPrivateKeySpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECAlgorithms;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
import static org.bitcoinj.core.ECKey.CURVE;
import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER;
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
public final class BTCUtils {
static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N
public static final long MIN_FEE_PER_KB = 10000;
public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1");
public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000;
public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L;
public static final int MAX_TX_LEN_FOR_NO_FEE = 10000;
public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day)
public static long calcMinimumFee(int txLen, Collection<UnspentOutputInfo> unspentOutputInfos, long minOutput) {
if (isZeroFeeAllowed(txLen, unspentOutputInfos, minOutput)) {
return 0;
}
return MIN_FEE_PER_KB * (1 + txLen / 1000);
}
public static boolean isZeroFeeAllowed(int txLen, Collection<UnspentOutputInfo> unspentOutputInfos, long minOutput) {
if (txLen < MAX_TX_LEN_FOR_NO_FEE && minOutput > MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE) {
long priority = 0;
for (UnspentOutputInfo output : unspentOutputInfos) {
if (output.confirmations > 0) {
priority += output.confirmations * output.value;
}
}
priority /= txLen;
if (priority > MIN_PRIORITY_FOR_NO_FEE) {
return true;
}
}
return false;
}
public static int getMaximumTxSize(Collection<UnspentOutputInfo> unspentOutputInfos, int outputsCount, boolean compressedPublicKey) throws BitcoinException {
if (unspentOutputInfos == null || unspentOutputInfos.isEmpty()) {
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No information about tx inputs provided");
}
int maxInputScriptLen = 73 + (compressedPublicKey ? 33 : 65);
return 9 + unspentOutputInfos.size() * (41 + maxInputScriptLen) + outputsCount * 33;
}
public static String publicKeyToAddress(byte[] publicKey) {
return publicKeyToAddress(false, publicKey);
}
public static String publicKeyToAddress(boolean testNet, byte[] publicKey) {
try {
byte[] hashedPublicKey = CryptoUtil.sha256ripemd160(publicKey);
byte[] addressBytes = new byte[1 + hashedPublicKey.length + 4];
addressBytes[0] = (byte) (testNet ? 111 : 0);
System.arraycopy(hashedPublicKey, 0, addressBytes, 1, hashedPublicKey.length);
MessageDigest digestSha = MessageDigest.getInstance("SHA-256");
digestSha.update(addressBytes, 0, addressBytes.length - 4);
byte[] check = digestSha.digest(digestSha.digest());
System.arraycopy(check, 0, addressBytes, hashedPublicKey.length + 1, 4);
return Base58.encodeBase58(addressBytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String toHex(byte[] bytes) {
if (bytes == null) {
return "";
}
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException {
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
unspentOutputs.get(currentInputPos).scriptForBuild = myScript;
int inputPos = currentInputPos;
byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(body);
os.write(new byte[]{0x01, 0x00, 0x00, 0x00});
byte[] tx = os.toByteArray();
return tx;
}
public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, long amount, long change) throws BitcoinException, IOException {
int inputPos = -1;
byte[] body = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(body);
byte[] tx = os.toByteArray();
return tx;
}
public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
//0200000000
BitcoinOutputStream forSign = new BitcoinOutputStream();
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//01
byte inputCount = (byte)unspentOutputs.size();
forSign.write(inputCount); // input count
//hex str hash prev btc
for(int i = 0; i < inputCount; ++i)
{
UnspentOutputInfo outPut = unspentOutputs.get(i);
int outputIndex = outPut.outputIndex;
byte[] txHash = BTCUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte);
forSign.write(txHash);
forSign.writeInt32(outputIndex); //output index in prev tx
if(inputPos ==-1 || i == inputPos)
{
// hex str 1976a914....88ac
forSign.write((byte)outPut.scriptForBuild.length);
forSign.write(outPut.scriptForBuild);
}
else
{
forSign.write(0x00);
}
//ffffffff
forSign.write(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}); // sequence
}
//02
byte outputCount = (byte)((change==0) ? 1 : 2); // outputCount
forSign.write(outputCount);
//8 bytes
forSign.writeInt64(amount); //amount
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
//hex str 1976a914....88ac
forSign.write((byte)sendScript.length);
forSign.write(sendScript);
if(change!=0){
//8 bytes
forSign.writeInt64(change); // change
//hex str 1976a914....88ac
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
forSign.write((byte)chancheScript.length);
forSign.write(chancheScript);
}
//00000000
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
//forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
Log.e("Sign_TX_Body", BTCUtils.toHex(rawData));
return rawData;
}
int calculateSize(int inputCount, int outputCount)
{
int size = 0;
size += 4; // header
size += 1; //inputCount
//hex str hash prev btc
for(int i = 0; i < inputCount; ++i)
{
size += 32; //prevtx
size += 4; //outputIndex;
size += 1; //scriptLength
// size+=script;
size += 4; //ffffffff
}
size+=1; //outputCount
size+=8; //amount
size+=1;
// size+=script;
if(outputCount > 1)
{
size+=8;
size+=1;
//scriptLen;
}
size+=4;
return size;
}
public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException {
//0200000000
BitcoinOutputStream forSign = new BitcoinOutputStream();
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//01
byte inputCount = 1;
forSign.write(inputCount); // input count
//hex str hash prev btc
byte[] txHash = BTCUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte);
forSign.write(txHash); //previos tx hash
//00000000
//byte indexOutput = outputIndex;
forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx
//forSign.write(0x00);
// hex str 1976a914....88ac
forSign.write((byte)script.length);
forSign.write(script);
//ffffffff
forSign.write(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}); // sequence
//02
byte outputCount = (byte)((change==0) ? 1 : 2); // outputCount
forSign.write(outputCount);
//8 bytes
forSign.writeInt64(amount); //amount
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
//hex str 1976a914....88ac
forSign.write((byte)sendScript.length);
forSign.write(sendScript);
if(change!=0){
//8 bytes
forSign.writeInt64(change); // change
//hex str 1976a914....88ac
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
forSign.write((byte)chancheScript.length);
forSign.write(chancheScript);
}
//00000000
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
//Log.e("Sign_TX_Body", BTCUtils.toHex(rawData));
return rawData;
}
public static ArrayList<byte[]> getPrevTX(String hex) throws BitcoinException {
byte[] rawTxByte = fromHex(hex);
Transaction baseTx = new Transaction(rawTxByte);
ArrayList<byte[]> prevHashes = new ArrayList<byte[]>();
for(int i =0; i < baseTx.inputs.length; ++i)
{
Transaction.Input input = baseTx.inputs[i];
prevHashes.add(input.outPoint.hash);
}
return prevHashes;
}
public static boolean isInput(String myAddress, String hex) throws BitcoinException {
byte[] rawTxByte = fromHex(hex);
Transaction baseTx = new Transaction(rawTxByte);
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
for(int i =0; i < baseTx.inputs.length; ++i)
{
Transaction.Input input = baseTx.inputs[i];
byte[] script = input.script.bytes;
// find outputs
if (Arrays.equals(myScript, script)){
return true;
}
}
return false;
}
public static ArrayList<UnspentOutputInfo> getOutputs(List<Tangem_Card.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
for(Tangem_Card.UnspentTransaction current: rawTxList)
{
byte[] rawTxByte = BTCUtils.fromHex(current.Raw);
if (rawTxByte == null)
{
continue;
}
Transaction baseTx = new Transaction(rawTxByte);
if(baseTx.inputs.length == 0 || baseTx.outputs.length == 0)
throw new IllegalArgumentException("Unable to decode given transaction");
byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte));
String txHashForBuild = current.txID;
byte[] sign = null;
for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) {
Transaction.Output output = baseTx.outputs[outputIndex];
// find outputs
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign));
}
}
}
return unspentOutputs;
}
public static byte[] fromHex(String s) {
if (s != null) {
try {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (!Character.isWhitespace(ch)) {
sb.append(ch);
}
}
s = sb.toString();
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int hi = (Character.digit(s.charAt(i), 16) << 4);
int low = Character.digit(s.charAt(i + 1), 16);
if (hi >= 256 || low < 0 || low >= 16) {
return null;
}
data[i / 2] = (byte) (hi | low);
}
return data;
} catch (Exception ignored) {
}
}
return null;
}
public static byte[] reverse(byte[] bytes) {
byte[] result = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
result[i] = bytes[bytes.length - i - 1];
}
return result;
}
public static byte[] reverseInPlace(byte[] bytes) {
int len = bytes.length / 2;
for (int i = 0; i < len; i++) {
byte t = bytes[i];
bytes[i] = bytes[bytes.length - i - 1];
bytes[bytes.length - i - 1] = t;
}
return bytes;
}
public static int findSpendableOutput(Transaction tx, String forAddress, long minAmount) throws BitcoinException {
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(forAddress).bytes;
int indexOfOutputToSpend = -1;
for (int indexOfOutput = 0; indexOfOutput < tx.outputs.length; indexOfOutput++) {
Transaction.Output output = tx.outputs[indexOfOutput];
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
indexOfOutputToSpend = indexOfOutput;
break;//only one input is supported for now
}
}
if (indexOfOutputToSpend == -1) {
throw new BitcoinException(BitcoinException.ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS, "No spendable standard outputs for " + forAddress + " have found", forAddress);
}
final long spendableOutputValue = tx.outputs[indexOfOutputToSpend].value;
if (spendableOutputValue < minAmount) {
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Unspent amount is too small: " + spendableOutputValue, spendableOutputValue);
}
return indexOfOutputToSpend;
}
public static void verify(Transaction.Script[] scripts, Transaction spendTx) throws Transaction.Script.ScriptInvalidException {
for (int i = 0; i < scripts.length; i++) {
Stack<byte[]> stack = new Stack<>();
spendTx.inputs[i].script.run(stack);//load signature+public key
scripts[i].run(i, spendTx, stack); //verify that this transaction able to spend that output
if (Transaction.Script.verifyFails(stack)) {
throw new Transaction.Script.ScriptInvalidException("Signature is invalid");
}
}
}
public static class FeeChangeAndSelectedOutputs {
public final long amountForRecipient, change, fee;
public final ArrayList<UnspentOutputInfo> outputsToSpend;
public FeeChangeAndSelectedOutputs(long fee, long change, long amountForRecipient, ArrayList<UnspentOutputInfo> outputsToSpend) {
this.fee = fee;
this.change = change;
this.amountForRecipient = amountForRecipient;
this.outputsToSpend = outputsToSpend;
}
}
public static FeeChangeAndSelectedOutputs calcFeeChangeAndSelectOutputsToSpend(List<UnspentOutputInfo> unspentOutputs, long amountToSend, long extraFee, final boolean isPublicKeyCompressed) throws BitcoinException {
long fee = 0;//calculated below
long change = 0;
long valueOfUnspentOutputs;
ArrayList<UnspentOutputInfo> outputsToSpend = new ArrayList<>();
if (amountToSend <= 0) {
//transfer all funds from these addresses to outputAddress
change = 0;
valueOfUnspentOutputs = 0;
for (UnspentOutputInfo outputInfo : unspentOutputs) {
outputsToSpend.add(outputInfo);
valueOfUnspentOutputs += outputInfo.value;
}
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, 1, isPublicKeyCompressed);
fee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, valueOfUnspentOutputs - MIN_FEE_PER_KB * (1 + txLen / 1000));
amountToSend = valueOfUnspentOutputs - fee - extraFee;
} else {
valueOfUnspentOutputs = 0;
for (UnspentOutputInfo outputInfo : unspentOutputs) {
outputsToSpend.add(outputInfo);
valueOfUnspentOutputs += outputInfo.value;
long updatedFee = MIN_FEE_PER_KB;
for (int i = 0; i < 3; i++) {
fee = updatedFee;
change = valueOfUnspentOutputs - fee - extraFee - amountToSend;
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, change > 0 ? 2 : 1, isPublicKeyCompressed);
updatedFee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, change > 0 ? Math.min(amountToSend, change) : amountToSend);
if (updatedFee == fee) {
break;
}
}
fee = updatedFee;
if (valueOfUnspentOutputs >= amountToSend + fee + extraFee) {
break;
}
}
}
if (amountToSend > valueOfUnspentOutputs - fee) {
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Not enough funds", valueOfUnspentOutputs - fee);
}
if (outputsToSpend.isEmpty()) {
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No outputs to spend");
}
if (fee + extraFee > MAX_ALLOWED_FEE) {
throw new BitcoinException(BitcoinException.ERR_FEE_IS_TOO_BIG, "Fee is too big", fee);
}
if (fee < 0 || extraFee < 0) {
throw new BitcoinException(BitcoinException.ERR_FEE_IS_LESS_THEN_ZERO, "Incorrect fee", fee);
}
if (change < 0) {
throw new BitcoinException(BitcoinException.ERR_CHANGE_IS_LESS_THEN_ZERO, "Incorrect change", change);
}
if (amountToSend < 0) {
throw new BitcoinException(BitcoinException.ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO, "Incorrect amount to send", amountToSend);
}
return new FeeChangeAndSelectedOutputs(fee + extraFee, change, amountToSend, outputsToSpend);
}
}

View file

@ -0,0 +1,115 @@
package com.tangem.wallet;
import java.math.BigInteger;
/**
* Created by Ilia on 15.02.2018.
*/
public class Base58 {
private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long
private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS
private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1,
-1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
-1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
public static byte[] decodeBase58(String input) {
if (input == null) {
return null;
}
input = input.trim();
if (input.length() == 0) {
return new byte[0];
}
BigInteger resultNum = BigInteger.ZERO;
int nLeadingZeros = 0;
while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) {
nLeadingZeros++;
}
long acc = 0;
int nDigits = 0;
int p = nLeadingZeros;
while (p < input.length()) {
int v = BASE58_VALUES[input.charAt(p) & 0xff];
if (v >= 0) {
acc *= 58;
acc += v;
nDigits++;
if (nDigits == BASE58_CHUNK_DIGITS) {
resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc));
acc = 0;
nDigits = 0;
}
p++;
} else {
break;
}
}
if (nDigits > 0) {
long mul = 58;
while (--nDigits > 0) {
mul *= 58;
}
resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc));
}
final int BASE58_SPACE = -2;
while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) {
p++;
}
if (p < input.length()) {
return null;
}
byte[] plainNumber = resultNum.toByteArray();
int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0;
byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs];
System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs);
return result;
}
public static String encodeBase58(byte[] input) {
if (input == null) {
return null;
}
StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1);
BigInteger bn = new BigInteger(1, input);
long rem;
while (true) {
BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD);
bn = divideAndRemainder[0];
rem = divideAndRemainder[1].longValue();
if (bn.compareTo(BigInteger.ZERO) == 0) {
break;
}
for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) {
str.append(BASE58[(int) (rem % 58)]);
rem /= 58;
}
}
while (rem != 0) {
str.append(BASE58[(int) (rem % 58)]);
rem /= 58;
}
str.reverse();
int nLeadingZeros = 0;
while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) {
str.insert(0, BASE58[0]);
nLeadingZeros++;
}
return str.toString();
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
@SuppressWarnings("WeakerAccess")
public final class BitcoinException extends Exception {
public static final int ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS = 0;
public static final int ERR_INSUFFICIENT_FUNDS = 1;
public static final int ERR_WRONG_TYPE = 2;
public static final int ERR_BAD_FORMAT = 3;
public static final int ERR_INCORRECT_PASSWORD = 4;
public static final int ERR_MEANINGLESS_OPERATION = 5;
public static final int ERR_NO_INPUT = 6;
public static final int ERR_FEE_IS_TOO_BIG = 7;
public static final int ERR_FEE_IS_LESS_THEN_ZERO = 8;
public static final int ERR_CHANGE_IS_LESS_THEN_ZERO = 9;
public static final int ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO = 10;
public static final int ERR_UNSUPPORTED = 11;
public final int errorCode;
@SuppressWarnings({"WeakerAccess", "unused"})
public final Object extraInformation;
public BitcoinException(int errorCode, String detailMessage, Object extraInformation) {
super(detailMessage);
this.errorCode = errorCode;
this.extraInformation = extraInformation;
}
public BitcoinException(int errorCode, String detailMessage) {
this(errorCode, detailMessage, null);
}
}

View file

@ -0,0 +1,69 @@
package com.tangem.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
@SuppressWarnings("WeakerAccess")
public class BitcoinInputStream extends ByteArrayInputStream {
public BitcoinInputStream(byte[] buf) {
super(buf);
}
@SuppressWarnings("unused")
public BitcoinInputStream(byte[] buf, int offset, int length) {
super(buf, offset, length);
}
public int readInt16() throws EOFException {
return (readByte() & 0xff) | ((readByte() & 0xff) << 8);
}
public int readInt32() throws EOFException {
return (readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 24);
}
public long readInt64() throws EOFException {
return (readInt32() & 0xFFFFFFFFL )| ((readInt32() & 0xFFFFFFFFL) << 32);
}
public int readByte() throws EOFException {
int readedByte = super.read();
if (readedByte == -1) {
throw new EOFException();
}
return readedByte;
}
public long readVarInt() throws EOFException {
int readedByte = readByte();
if (readedByte < 0xfd) {
return readedByte;
} else if (readedByte == 0xfd) {
return readInt16();
} else if (readedByte == 0xfe) {
return readInt32();
} else {
return readInt64();
}
}
public byte[] readChars(final int count) throws IOException {
byte[] buf = new byte[count];
int off = 0;
while (off != count) {
int bytesReadCurr = read(buf, off, count - off);
if (bytesReadCurr == -1) {
throw new EOFException();
} else {
off += bytesReadCurr;
}
}
return buf;
}
}

View file

@ -0,0 +1,42 @@
package com.tangem.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
import java.io.ByteArrayOutputStream;
@SuppressWarnings("WeakerAccess")
public final class BitcoinOutputStream extends ByteArrayOutputStream {
public void writeInt16(int value) {
write(value & 0xff);
write((value >> 8) & 0xff);
}
public void writeInt32(int value) {
write(value & 0xff);
write((value >> 8) & 0xff);
write((value >> 16) & 0xff);
write((value >>> 24) & 0xff);
}
public void writeInt64(long value) {
writeInt32((int) (value & 0xFFFFFFFFL));
writeInt32((int) ((value >>> 32) & 0xFFFFFFFFL));
}
public void writeVarInt(long value) {
if (value < 0xfd) {
write((int) (value & 0xff));
} else if (value < 0xffff) {
write(0xfd);
writeInt16((int) value);
} else if (value < 0xffffffffL) {
write(0xfe);
writeInt32((int) value);
} else {
write(0xff);
writeInt64(value);
}
}
}

View file

@ -0,0 +1,99 @@
package com.tangem.wallet;
import android.net.Uri;
import com.google.common.base.Strings;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.Util;
import org.bitcoinj.core.Base58;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Created by dvol on 06.08.2017.
*/
public enum Blockchain {
Unknown("", "", 1.0, R.drawable.ic_logo_small, ""),
Bitcoin("BTC", "BTC", 100000000.0, R.drawable.bitcoins, "Bitcoin"),
BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.bitcoins_testnet, "Bitcoin Testnet"),
Ethereum("ETH", "ETH", 1.0, R.drawable.ethereum, "Ethereum"),
EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ethereum_testnet, "Ethereum Testnet"),
Token("ETH\\XTZ", "BAT", 1.0, R.drawable.bat_token, "Ethereum"),
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.bitcoin_cash, "Bitcoin Cash"),
BitcoinCashTestNet("BCH/test", "BTC", 100000000.0, R.drawable.bitcoin_cash, "Bitcoin Cash Testnet");
Blockchain(String ID, String Currency, double Multiplier, int ImageResource, String officialName) {
mID = ID;
mCurrency = Currency;
mMultiplier = Multiplier;
mImageResource = ImageResource;
mOfficialName = officialName;
}
private String mID, mOfficialName;
private double mMultiplier;
private String mCurrency;
private int mImageResource;
public String getID() {
return mID;
}
public String getOfficialName() {
return mOfficialName;
}
public double getMultiplier() {
return mMultiplier;
}
public String getCurrency() {
return mCurrency;
}
public static Blockchain fromId(String id) {
for (Blockchain blockchain : values()) {
if (blockchain.getID().equals(id)) return blockchain;
}
return null;
}
public static Blockchain fromCurrency(String currency) {
for (Blockchain blockchain : values()) {
if (blockchain.getCurrency() == currency) return blockchain;
}
return null;
}
public static String[] getCurrencies() {
String[] result = new String[values().length - 1];
for (int i = 0; i < result.length - 1; i++) {
result[i] = values()[i + 1].getCurrency();
}
return result;
}
public int getImageResource() {
return mImageResource;
}
public int getImageResource(android.content.Context context, String name) {
if(Strings.isNullOrEmpty(name))
return getImageResource();
name = name.toLowerCase();
int resourceId = context.getResources().getIdentifier(name+"_token", "drawable", context.getPackageName());
if(resourceId <= 0)
return R.drawable.ethereum;
return resourceId;
}
}

View file

@ -0,0 +1,428 @@
package com.tangem.wallet;
import android.net.Uri;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.TLV;
import com.tangem.cardReader.Util;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static com.tangem.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class BtcCashEngine extends CoinEngine{
public String GetNextNode(Tangem_Card mCard)
{
return "35.157.238.5";
}
public int GetNextNodePort(Tangem_Card mCard)
{
return 51001;
}
public String GetNode(Tangem_Card mCard)
{
return "35.157.238.5";
}
public int GetNodePort(Tangem_Card mCard)
{
return 51001;
}
public void SwitchNode(Tangem_Card mCard)
{
}
public boolean InOutPutVisible()
{
return true;
}
public boolean AwaitingConfirmation(Tangem_Card card)
{
return card.getBalanceUnconfirmed()!=0;
}
public String GetBalanceWithAlter(Tangem_Card mCard)
{
return GetBalance(mCard);
}
public boolean IsBalanceAlterNotZero(Tangem_Card card)
{
return true;
}
public Long GetBalanceLong(Tangem_Card mCard)
{
return mCard.getBalance();
}
public boolean IsBalanceNotZero(Tangem_Card card)
{
return card.getBalance() > 0;
}
public boolean CheckAmount(Tangem_Card card, String amount) throws Exception
{
DecimalFormat decimalFormat = GetDecimalFormat();
BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount);
// Convert Balance to BigDecimal
BigDecimal maxValue = new BigDecimal(GetBalanceValue(card));
maxValue = maxValue.divide(new BigDecimal(1000));
//if (use_mCurrency) {
amountValue = amountValue.divide(new BigDecimal(1000));
//}
if (amountValue.compareTo(maxValue) > 0) {
return false;
}
return true;
}
public boolean HasBalanceInfo(Tangem_Card card)
{
return card.hasBalanceInfo();
}
public String GetBalanceCurrency(Tangem_Card card)
{
return "mBCH";
}
public boolean CheckUnspentTransaction(Tangem_Card mCard)
{
return mCard.getUnspentTransactions().size() != 0;
}
public String GetFeeCurrency()
{
return "mBCH";
}
public boolean ValdateAddress(String address, Tangem_Card card){
if(address == null || address.isEmpty())
{
return false;
}
if(address.length() < 25)
{
return false;
}
if(address.length() > 35)
{
return false;
}
if(!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m"))
{
return false;
}
byte[] decAddress = Base58.decodeBase58(address);
if(decAddress == null || decAddress.length == 0)
{
return false;
}
byte[] rip = new byte[21];
for(int i =0; i < 21; ++i)
{
rip[i] = decAddress[i];
}
byte[] kcv = CryptoUtil.doubleSha256(rip);
for(int i =0; i < 4; ++i)
{
if(kcv[i] != decAddress[21+i])
return false;
}
if(card.getBlockchain()!=Blockchain.BitcoinCashTestNet && card.getBlockchain()!=Blockchain.BitcoinCash)
{
return false;
}
if(card.getBlockchain()==Blockchain.BitcoinCashTestNet && (address.startsWith("1") || address.startsWith("3")))
{
return false;
}
return true;
}
public int GetTokenDecimals(Tangem_Card card)
{
return 0;
}
public String GetContractAddress(Tangem_Card card)
{
return "";
}
public boolean IsNeedCheckNode()
{
return true;
}
public Uri getShareWalletURIExplorer(Tangem_Card mCard)
{
return Uri.parse((mCard.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + mCard.getWallet());
}
public Uri getShareWalletURI(Tangem_Card mCard)
{
return Uri.parse("bitcoincash:" + mCard.getWallet());
}
public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits)
{
Long fee = null;
Long amount = null;
try {
amount = mCard.InternalUnitsFromString(amountValue);
fee = mCard.InternalUnitsFromString(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if(fee == null || amount == null)
return false;
if(fee == 0 || amount ==0)
return false;
if(fee > amount)
return false;
if(fee < minFeeInInternalUnits)
return false;
return true;
}
public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee)
{
return GetAmountEqualentDescriptor(mCard, fee);
}
@Override
public String GetBalanceEquivalent(Tangem_Card mCard) {
Double balance = Double.NaN;
try{
Long val = mCard.getBalance();
balance = mCard.AmountFromInternalUnits(val);
}
catch(Exception ex)
{
mCard.setRate(0);
}
return mCard.getAmountEquivalentDescription(balance);
}
public String GetBalance(Tangem_Card mCard)
{
if (mCard.hasBalanceInfo()) {
Double balance = mCard.AmountFromInternalUnits(mCard.getBalance());
return mCard.getAmountDescription(balance);
} else {
return "-- -- -- " + mCard.getBlockchain().getCurrency();
}
}
public String GetBalanceValue(Tangem_Card mCard)
{
if (mCard.hasBalanceInfo()) {
Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier() / 1000.0);
String output = FormatUtil.DoubleToString(balance);
//String pattern = "#0.000"; // If you like 4 zeros
//DecimalFormat myFormatter = new DecimalFormat(pattern);
//String output = myFormatter.format(balance);
return output;
//return Double.toString(balance);
}
else
{
return "0";
}
}
public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
byte netSelectionByte;
switch (mCard.getBlockchain()) {
case BitcoinCash:
netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet
break;
default :
netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet
break;
}
byte hash1[] = Util.calculateSHA256(pkUncompressed);
byte hash2[] = Util.calculateRIPEMD160(hash1);
ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1);
BB.put(netSelectionByte);
BB.put(hash2);
byte hash3[] = Util.calculateSHA256(BB.array());
byte hash4[] = Util.calculateSHA256(hash3);
BB = ByteBuffer.allocate(hash2.length + 5);
BB.put(netSelectionByte); //BB.put((byte) 0x6f);
BB.put(hash2);
BB.put(hash4[0]);
BB.put(hash4[1]);
BB.put(hash4[2]);
BB.put(hash4[3]);
return org.bitcoinj.core.Base58.encode(BB.array());
}
@Override
public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception {
byte[] reversed=new byte[bytes.length];
for(int i=0; i<bytes.length; i++) reversed[i]=bytes[bytes.length-i-1];
return FormatUtil.DoubleToString(1000.0*mCard.AmountFromInternalUnits(Util.byteArrayToLong(reversed)));
}
@Override
public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception {
byte[] bytes=Util.longToByteArray(mCard.InternalUnitsFromString(amount));
byte[] reversed=new byte[bytes.length];
for(int i=0; i<bytes.length; i++) reversed[i]=bytes[bytes.length-i-1];
return reversed;
}
@Override
public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception {
return mCard.getAmountDescription(Double.parseDouble(amount)/1000.0);
}
public static String getAmountEquivalentDescriptionBTC(Double amount, float rate) {
if (rate > 0) {
return String.format("USD%.2f", amount * rate);
} else {
return "USD ---";
}
}
public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value)
{
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value)/1000.0, mCard.getRate());
}
public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception {
String myAddress = mCard.getWallet();
byte[] pbKey = mCard.getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
String outputAddress = toValue;
String changeAddress = myAddress;
// Build script for our address
List<Tangem_Card.UnspentTransaction> rawTxList = mCard.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
fullAmount += unspentOutputs.get(i).value;
}
long fees = FormatUtil.ConvertStringToLong(feeValue);
long amount = FormatUtil.ConvertStringToLong(amountValue);
amount = amount - fees;
long change = fullAmount - fees - amount;
if (amount + fees > fullAmount) {
throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount));
}
byte[][] dataForSign = new byte[unspentOutputs.size()][];
for (int i = 0; i < unspentOutputs.size(); ++i) {
byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change);
byte[] hashData = Util.calculateSHA256(newTX);
byte[] doubleHashData = Util.calculateSHA256(hashData);
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer)
{
dataForSign[i] = newTX;
}
else
{
dataForSign[i] = doubleHashData;
}
}
byte[] signFromCard = null;
if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer)
{
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < dataForSign.length; i++) {
if (i != 0 && dataForSign[0].length != dataForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(dataForSign[i]);
}
signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value;
}
else {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
byte[] encodingSign = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX;
}
}

View file

@ -0,0 +1,564 @@
package com.tangem.wallet;
import android.net.Uri;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.TLV;
import com.tangem.cardReader.Util;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;
import static com.tangem.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class BtcEngine extends CoinEngine{
public String GetNextNode(Tangem_Card mCard)
{
return getNextServiceHost(mCard);
}
public int GetNextNodePort(Tangem_Card mCard)
{
return getNextServicePort(mCard);
}
public String GetNode(Tangem_Card mCard)
{
return getServiceHost(mCard);
}
public int GetNodePort(Tangem_Card mCard)
{
return getServicePort(mCard);
}
public void SwitchNode(Tangem_Card mCard)
{
SelectNextBitconServiceIndex();
}
public static String[] GetBitcoinServiceHosts() {
return new String[]{"vps.hsmiths.com", "tardis.bauerj.eu" /*"arihancckjge66iv.onion"*/, "electrumx.bot.nu","electrumx.hopto.org"/* "btc.asis.io"*/, "e-x.not.fyi", "electrum.backplanedns.org", "helicarrier.bauerj.eu", "electrum.vom-stausee.de", "electrum0.snel.it", "kirsche.emzy.de"};
}
public static String[] GetBitcoinTestNetServiceHosts() {
return new String[]{/*"testnetnode.arihanc.com"*/"testnet.hsmiths.com", "testnet.qtornado.com", "testnet1.bauerj.eu"};
}
public static Integer[] GetBitcoinServicePorts() {
return new Integer[]{8080,50001/* 8080*/, 50001, 50001, 50001, 50001, 50001, 50001, 50001, 50001};
}
public static Integer[] GetBitcoinTestNetServicePorts() {
return new Integer[]{/*51001*/53011, 51001, 50001};
}
static int serviceIndex = GetNextBitconMainNetServiceIndex();
static int dynamicIndex = GetNextBitconMainNetServiceIndex();
static int serviceIndexTestNet = GetNextBitconTestNetServiceIndex();
static int dynamicTestNetIndex = GetNextBitconTestNetServiceIndex();
static long lastChangeServiceIndex = 0;
public static int GetNextBitconMainNetServiceIndex() {
Random r = new Random();
return r.nextInt(GetBitcoinServiceHosts().length);
}
public static void SelectNextBitconMainNetServiceIndex() {
//serviceIndex = GetNextBitconMainNetServiceIndex();
serviceIndex++;
if (serviceIndex > GetBitcoinServiceHosts().length - 1) serviceIndex = 0;
}
public static int GetNextBitconTestNetServiceIndex() {
Random r = new Random();
return r.nextInt(GetBitcoinTestNetServiceHosts().length);
}
public static void SelectNextBitconTestNetServiceIndex() {
//serviceIndexTestNet = GetNextBitconTestNetServiceIndex();
serviceIndexTestNet++;
if (serviceIndexTestNet > GetBitcoinTestNetServiceHosts().length - 1) serviceIndexTestNet = 0;
}
public static void setNextDynamicIndex() {
//dynamicIndex = GetNextBitconMainNetServiceIndex();
dynamicIndex++;
if (dynamicIndex > GetBitcoinServiceHosts().length - 1) dynamicIndex = 0;
}
public static void setNextDynamicTestNet() {
//dynamicTestNetIndex = GetNextBitconTestNetServiceIndex();
dynamicTestNetIndex++;
if (dynamicTestNetIndex > GetBitcoinTestNetServiceHosts().length - 1) dynamicTestNetIndex = 0;
}
public static void SelectNextBitconServiceIndex() {
long unixTime = System.currentTimeMillis() / 1000L;
long nextStampOffset = 5;
if(lastChangeServiceIndex == 0)
{
lastChangeServiceIndex = unixTime;
}
else if(lastChangeServiceIndex + nextStampOffset > unixTime )
{
return;
}
lastChangeServiceIndex = unixTime;
SelectNextBitconMainNetServiceIndex();
SelectNextBitconTestNetServiceIndex();
}
public static String getServiceHost(Tangem_Card mCard) {
switch (mCard.getBlockchain()) {
case Bitcoin:
return GetBitcoinServiceHosts()[serviceIndex]; //"hsmiths.changeip.net";
case BitcoinTestNet:
return GetBitcoinTestNetServiceHosts()[serviceIndexTestNet]; //"testnetnode.arihanc.com";
}
return null;
}
public static String getNextServiceHost(Tangem_Card mCard) {
switch (mCard.getBlockchain()) {
case Bitcoin: {
setNextDynamicIndex();
return GetBitcoinServiceHosts()[dynamicIndex];
}
case BitcoinTestNet: {
setNextDynamicTestNet();
return GetBitcoinTestNetServiceHosts()[dynamicTestNetIndex]; //"testnetnode.arihanc.com";
}
//case BitcoinCash:
//{
// return GetBitcoinCashServiceHosts()[0];
//}
//case BitcoinCashTestNet: {
// return GetBitcoinCashTestNetServiceHosts()[0];
//}
}
return null;
}
public static int getNextServicePort(Tangem_Card mCard) {
switch (mCard.getBlockchain()) {
case Bitcoin: {
setNextDynamicIndex();
return GetBitcoinServicePorts()[dynamicIndex];//8080;
}
case BitcoinTestNet: {
setNextDynamicTestNet();
return GetBitcoinTestNetServicePorts()[dynamicTestNetIndex];//51001;
}
}
return 8080;
}
public static int getServicePort(Tangem_Card mCard) {
switch (mCard.getBlockchain()) {
case Bitcoin:
return GetBitcoinServicePorts()[serviceIndex];//8080;
case BitcoinTestNet:
return GetBitcoinTestNetServicePorts()[serviceIndexTestNet];//51001;
}
return 8080;
}
public boolean InOutPutVisible()
{
return true;
}
public boolean AwaitingConfirmation(Tangem_Card card)
{
return card.getBalanceUnconfirmed()!=0;
}
public String GetBalanceWithAlter(Tangem_Card mCard)
{
return GetBalance(mCard);
}
public boolean IsBalanceAlterNotZero(Tangem_Card card)
{
return true;
}
public Long GetBalanceLong(Tangem_Card mCard)
{
return mCard.getBalance();
}
public boolean IsBalanceNotZero(Tangem_Card card)
{
return card.getBalance() > 0;
}
public boolean CheckAmount(Tangem_Card card, String amount) throws Exception
{
DecimalFormat decimalFormat = GetDecimalFormat();
BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount);
// Convert Balance to BigDecimal
BigDecimal maxValue = new BigDecimal(GetBalanceValue(card));
maxValue = maxValue.divide(new BigDecimal(1000));
//if (use_mCurrency) {
amountValue = amountValue.divide(new BigDecimal(1000));
//}
if (amountValue.compareTo(maxValue) > 0) {
return false;
}
return true;
}
public boolean HasBalanceInfo(Tangem_Card card)
{
return card.hasBalanceInfo();
}
public String GetBalanceCurrency(Tangem_Card card)
{
return "mBTC";
}
public boolean CheckUnspentTransaction(Tangem_Card mCard)
{
return mCard.getUnspentTransactions().size() != 0;
}
public String GetFeeCurrency()
{
return "mBTC";
}
public boolean ValdateAddress(String address, Tangem_Card card){
if(address == null || address.isEmpty())
{
return false;
}
if(address.length() < 25)
{
return false;
}
if(address.length() > 35)
{
return false;
}
if(!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m"))
{
return false;
}
byte[] decAddress = Base58.decodeBase58(address);
if(decAddress == null || decAddress.length == 0)
{
return false;
}
byte[] rip = new byte[21];
for(int i =0; i < 21; ++i)
{
rip[i] = decAddress[i];
}
byte[] kcv = CryptoUtil.doubleSha256(rip);
for(int i =0; i < 4; ++i)
{
if(kcv[i] != decAddress[21+i])
return false;
}
if(card.getBlockchain()!=Blockchain.BitcoinTestNet && card.getBlockchain()!=Blockchain.Bitcoin)
{
return false;
}
if(card.getBlockchain()==Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3")))
{
return false;
}
return true;
}
public int GetTokenDecimals(Tangem_Card card)
{
return 0;
}
public String GetContractAddress(Tangem_Card card)
{
return "";
}
public boolean IsNeedCheckNode()
{
return true;
}
public Uri getShareWalletURIExplorer(Tangem_Card mCard)
{
return Uri.parse((mCard.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + mCard.getWallet());
}
public Uri getShareWalletURI(Tangem_Card mCard)
{
return Uri.parse("bitcoin:" + mCard.getWallet());
}
public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits)
{
Long fee = null;
Long amount = null;
try {
amount = mCard.InternalUnitsFromString(amountValue);
fee = mCard.InternalUnitsFromString(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if(fee == null || amount == null)
return false;
if(fee == 0 || amount ==0)
return false;
if(fee > amount)
return false;
if(fee < minFeeInInternalUnits)
return false;
return true;
}
public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee)
{
return GetAmountEqualentDescriptor(mCard, fee);
}
@Override
public String GetBalanceEquivalent(Tangem_Card mCard) {
Double balance = Double.NaN;
try{
Long val = mCard.getBalance();
balance = mCard.AmountFromInternalUnits(val);
}
catch(Exception ex)
{
mCard.setRate(0);
}
return mCard.getAmountEquivalentDescription(balance);
}
public String GetBalance(Tangem_Card mCard)
{
if (mCard.hasBalanceInfo()) {
Double balance = mCard.AmountFromInternalUnits(mCard.getBalance());
return mCard.getAmountDescription(balance);
} else {
return "-- -- -- " + mCard.getBlockchain().getCurrency();
}
}
public String GetBalanceValue(Tangem_Card mCard)
{
if (mCard.hasBalanceInfo()) {
Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier() / 1000.0);
String output = FormatUtil.DoubleToString(balance);
//String pattern = "#0.000"; // If you like 4 zeros
//DecimalFormat myFormatter = new DecimalFormat(pattern);
//String output = myFormatter.format(balance);
return output;
//return Double.toString(balance);
}
else
{
return "0";
}
}
public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
byte netSelectionByte;
switch (mCard.getBlockchain()) {
case Bitcoin:
netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet
break;
default :
netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet
break;
}
byte hash1[] = Util.calculateSHA256(pkUncompressed);
byte hash2[] = Util.calculateRIPEMD160(hash1);
ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1);
BB.put(netSelectionByte);
BB.put(hash2);
byte hash3[] = Util.calculateSHA256(BB.array());
byte hash4[] = Util.calculateSHA256(hash3);
BB = ByteBuffer.allocate(hash2.length + 5);
BB.put(netSelectionByte); //BB.put((byte) 0x6f);
BB.put(hash2);
BB.put(hash4[0]);
BB.put(hash4[1]);
BB.put(hash4[2]);
BB.put(hash4[3]);
return org.bitcoinj.core.Base58.encode(BB.array());
}
@Override
public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception {
byte[] reversed=new byte[bytes.length];
for(int i=0; i<bytes.length; i++) reversed[i]=bytes[bytes.length-i-1];
return FormatUtil.DoubleToString(1000.0*mCard.AmountFromInternalUnits(Util.byteArrayToLong(reversed)));
}
@Override
public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception {
byte[] bytes=Util.longToByteArray(mCard.InternalUnitsFromString(amount));
byte[] reversed=new byte[bytes.length];
for(int i=0; i<bytes.length; i++) reversed[i]=bytes[bytes.length-i-1];
return reversed;
}
@Override
public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception {
return mCard.getAmountDescription(Double.parseDouble(amount)/1000.0);
}
public static String getAmountEquivalentDescriptionBTC(Double amount, float rate) {
if (rate > 0) {
return String.format("USD%.2f", amount * rate);
} else {
return "USD ---";
}
}
public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value)
{
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value)/1000.0, mCard.getRate());
}
public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception {
String myAddress = mCard.getWallet();
byte[] pbKey = mCard.getWalletPublicKey();
String outputAddress = toValue;
String changeAddress = myAddress;
// Build script for our address
List<Tangem_Card.UnspentTransaction> rawTxList = mCard.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
fullAmount += unspentOutputs.get(i).value;
}
long fees = FormatUtil.ConvertStringToLong(feeValue);
long amount = FormatUtil.ConvertStringToLong(amountValue);
amount = amount - fees;
long change = fullAmount - fees - amount;
if (amount + fees > fullAmount) {
throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount));
}
byte[][] dataForSign = new byte[unspentOutputs.size()][];
for (int i = 0; i < unspentOutputs.size(); ++i) {
byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change);
byte[] hashData = Util.calculateSHA256(newTX);
byte[] doubleHashData = Util.calculateSHA256(hashData);
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer)
{
dataForSign[i] = newTX;
}
else
{
dataForSign[i] = doubleHashData;
}
}
byte[] signFromCard = null;
if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer)
{
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < dataForSign.length; i++) {
if (i != 0 && dataForSign[0].length != dataForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(dataForSign[i]);
}
signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value;
}
else {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX;
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.wallet;
public class ByteUtil {
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static byte[] and(byte[] b1, byte[] b2) {
if (b1.length != b2.length) throw new RuntimeException("Array sizes differ");
byte[] ret = new byte[b1.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (byte) (b1[i] & b2[i]);
}
return ret;
}
public static byte[] or(byte[] b1, byte[] b2) {
if (b1.length != b2.length) throw new RuntimeException("Array sizes differ");
byte[] ret = new byte[b1.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (byte) (b1[i] | b2[i]);
}
return ret;
}
public static boolean isNullOrZeroArray(byte[] array){
return (array == null) || (array.length == 0);
}
public static boolean isSingleZero(byte[] array){
return (array.length == 1 && array[0] == 0);
}
public static int length(byte[]... bytes) {
int result = 0;
for (byte[] array : bytes) {
result += (array == null) ? 0 : array.length;
}
return result;
}
}

View file

@ -0,0 +1,122 @@
package com.tangem.wallet;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
public class CardInfoActivity extends AppCompatActivity implements WalletInfoFragment.OnFragmentInteractionListener {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
private Tangem_Card mCard;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_info);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
String UID = getIntent().getStringExtra("UID");
mCard = new Tangem_Card(UID);
mCard.LoadFromBundle(getIntent().getBundleExtra("Card"));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_card_info, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
if (position == 0) {
return WalletInfoFragment.newInstance(mCard);
} /*else if (position == 1) {
return WalletUnspentFragment.newInstance(mCard);
} else if (position == 2) {
return WalletHistoryFragment.newInstance(mCard);
}*/
return null;
}
@Override
public int getCount() {
// Show 3 total pages.
return 3;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Wallet info";
case 1:
return "Unspent";
case 2:
return "History";
}
return null;
}
}
}

View file

@ -0,0 +1,599 @@
package com.tangem.wallet;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.tangem.wallet.Tangem_Card.CountOurTx;
public class CardListAdapter extends RecyclerView.Adapter<CardListAdapter.CardViewHolder> {
public static class CardViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView tvBalance, tvOffline, tvBalanceEquivalent, tvWallet, tvInputs, tvStatusInBlockchain, tvCardID, tvLastInput, lbLastInput, lbLastOutput, tvLastOutput, tvBlockchain;
TextView tvType, tvTypeBg, tvVoid;
ImageView imgBlockchain, imgSecurityNotification;
View llCardLoaded, llCardEmpty, llCardError, llCardPurged;
CardViewHolder(View itemView) {
super(itemView);
cv = itemView.findViewById(R.id.cvCard);
tvBalance = itemView.findViewById(R.id.tvBalance);
tvOffline = itemView.findViewById(R.id.tvOffline);
tvBalanceEquivalent = itemView.findViewById(R.id.tvBalanceEquivalent);
tvWallet = itemView.findViewById(R.id.tvWallet);
tvCardID = itemView.findViewById(R.id.tvCardID);
tvType = itemView.findViewById(R.id.tvType);
tvTypeBg = itemView.findViewById(R.id.tvTypeBg);
tvVoid = itemView.findViewById(R.id.tvVoid);
if (tvCardID != null) {
tvCardID.requestFocus();
}
tvInputs = itemView.findViewById(R.id.tvInputs);
tvLastInput = itemView.findViewById(R.id.tvLastInput);
lbLastInput = itemView.findViewById(R.id.lbLastInput);
lbLastOutput = itemView.findViewById(R.id.lbLastOutput);
tvLastOutput = itemView.findViewById(R.id.tvLastOutput);
tvBlockchain = itemView.findViewById(R.id.tvBlockchain);
imgBlockchain = itemView.findViewById(R.id.imgBlockchain);
llCardLoaded = itemView.findViewById(R.id.cardLoaded);
llCardEmpty = itemView.findViewById(R.id.cardEmpty);
llCardError = itemView.findViewById(R.id.cardError);
llCardPurged = itemView.findViewById(R.id.cardPurged);
imgSecurityNotification = itemView.findViewById(R.id.imgSecurityNotification);
}
}
public interface UiCallbacks {
void onViewCard(Bundle cardInfo);
}
private LayoutInflater mLayoutInflater;
private Context mContext;
private UiCallbacks mUiCallbacks;
private List<Tangem_Card> mCards = new ArrayList<>(100);
public CardListAdapter(LayoutInflater layoutInflater, Bundle instate, UiCallbacks uiCallbacks) {
mLayoutInflater = layoutInflater;
mContext = layoutInflater.getContext();
mUiCallbacks = uiCallbacks;
if (instate != null) {
// restore state
ArrayList<String> UIDs = instate.getStringArrayList("card_UID");
if( UIDs!=null ) {
for (String UID : UIDs) {
Tangem_Card card = new Tangem_Card(UID);
card.LoadFromBundle(instate.getBundle(String.format("card_%s", UID)));
mCards.add(card);
}
}
}
}
public void onSaveInstanceState(Bundle outstate) {
ArrayList<String> UIDs = new ArrayList<String>(mCards.size());
for (Tangem_Card card : mCards) {
Bundle B = new Bundle();
card.SaveToBundle(B);
outstate.putBundle(String.format("card_%s", card.getUID()), B);
UIDs.add(card.getUID());
}
outstate.putStringArrayList("card_UID", UIDs);
}
public void clearCards() {
mCards.clear();
notifyDataSetChanged();
}
public Tangem_Card getCard(int cardIndex) {
return mCards.get(cardIndex);
}
public Tangem_Card getCardByWallet(String walletAddress) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
return c;
}
}
return null;
}
public void addCard(Tangem_Card card) {
mCards.add(0,card);
notifyDataSetChanged();
}
public void removeCard(int cardIndex) {
mCards.remove(cardIndex);
notifyItemRemoved(cardIndex);
}
public void removeCard(Tangem_Card card) {
int i;
for (i = 0; i < mCards.size(); i++) {
if (Arrays.equals(mCards.get(i).getCID(), card.getCID())) {
break;
}
}
if (i == mCards.size()) return;
removeCard(i);
}
public void updateCard(Tangem_Card card) {
int i;
for (i = 0; i < mCards.size(); i++) {
if (Arrays.equals(mCards.get(i).getCID(), card.getCID())) {
break;
}
}
if (i == mCards.size()) {
mCards.add(card);
} else {
mCards.remove(i);
mCards.add(i, card);
}
notifyDataSetChanged();
}
public void UpdateWalletBalance(String walletAddress, Long balanceConfirmed, Long balanceUnconfirmed, String validationNodeDescription) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setBalanceConfirmed(balanceConfirmed);
c.setBalanceUnconfirmed(balanceUnconfirmed);
c.setDecimalBalance(String.valueOf(balanceConfirmed));
c.setValidationNodeDescription(validationNodeDescription);
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletCoutConfirmTx(String walletAddress, BigInteger nonce) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.SetConfirmTXCount(nonce);
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletBlockchain(String walletAddress, Blockchain blockchain) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setBlockchainID(blockchain.getID());
notifyDataSetChanged();
return;
}
}
}
public void AddWalletBlockchainNameToken(String walletAddress) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.addTokenToBlockchainName();
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletBalance(String walletAddress, Long balanceConfirmed, String balanceString, String validationNodeDescription) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setBalanceConfirmed(balanceConfirmed);
c.setBalanceUnconfirmed(0L);
c.setDecimalBalance(String.valueOf(balanceString));
c.setValidationNodeDescription(validationNodeDescription);
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletBalanceOnlyAlter(String walletAddress, String balanceAlter) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setDecimalBalanceAlter(balanceAlter);
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletUnspent(String walletAddress, JSONArray jsUnspentArray) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
try {
c.getUnspentTransactions().clear();
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
Tangem_Card.UnspentTransaction trUnspent = new Tangem_Card.UnspentTransaction();
trUnspent.txID = jsUnspent.getString("tx_hash");
trUnspent.Amount = jsUnspent.getInt("value");
trUnspent.Height = jsUnspent.getInt("height");
c.getUnspentTransactions().add(trUnspent);
}
} catch (JSONException e) {
e.printStackTrace();
}
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletHistory(String walletAddress, JSONArray jsHistoryArray) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
try {
c.getHistoryTransactions().clear();
for (int i = 0; i < jsHistoryArray.length(); i++) {
JSONObject jsUnspent = jsHistoryArray.getJSONObject(i);
Tangem_Card.HistoryTransaction trHistory = new Tangem_Card.HistoryTransaction();
trHistory.txID = jsUnspent.getString("tx_hash");
trHistory.Height = jsUnspent.getInt("height");
c.getHistoryTransactions().add(trHistory);
}
} catch (JSONException e) {
e.printStackTrace();
}
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletHeader(String walletAddress, JSONObject jsHeader) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
try {
c.getHaedersInfo();
c.UpdateHeaderInfo(new Tangem_Card.HeaderInfo(
jsHeader.getInt("block_height"),
jsHeader.getInt("timestamp")));
} catch (JSONException e) {
e.printStackTrace();
}
notifyDataSetChanged();
return;
}
}
}
public void UpdateRate(String walletAddress, float rate) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setRate(rate);
notifyDataSetChanged();
break;
}
}
}
public void UpdateRateAlter(String walletAddress, float rate) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setRateAlter(rate);
notifyDataSetChanged();
break;
}
}
}
public void UpdateTransaction(String walletAddress, String txHash, String raw) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
List<Tangem_Card.UnspentTransaction> listTx = c.getUnspentTransactions();
for (Tangem_Card.UnspentTransaction tx : listTx) {
if (tx.txID.equals(txHash)) {
tx.Raw = raw;
}
}
List<Tangem_Card.HistoryTransaction> listHTx = c.getHistoryTransactions();
for (Tangem_Card.HistoryTransaction tx : listHTx) {
if (tx.txID.equals(txHash)) {
tx.Raw = raw;
CountOurTx(listHTx);
}
}
notifyDataSetChanged();
return;
}
}
}
public void UpdateWalletError(String walletAddress, String error) {
for (Tangem_Card c : mCards) {
if (c.getWallet().equals(walletAddress)) {
c.setError(error);
notifyDataSetChanged();
return;
}
}
}
@Override
public int getItemCount() {
return mCards == null ? 0 : mCards.size();
}
@Override
public CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_list_item, parent, false);
CardViewHolder cvh = new CardViewHolder(v);
return cvh;
}
@Override
public void onBindViewHolder(CardViewHolder holder, int position) {
try {
Tangem_Card card = mCards.get(position);
CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain());
int color = android.R.color.black;
switch (card.getStatus()) {
case NotPersonalized:
color = R.color.card_state_error;
holder.llCardError.setVisibility(View.VISIBLE);
holder.llCardLoaded.setVisibility(View.GONE);
holder.llCardEmpty.setVisibility(View.GONE);
holder.llCardPurged.setVisibility(View.GONE);
holder.imgBlockchain.setVisibility(View.INVISIBLE);
holder.imgSecurityNotification.setVisibility(View.INVISIBLE);
holder.tvCardID.setVisibility(View.INVISIBLE);
holder.tvCardID.setError(null);
holder.tvType.setVisibility(View.INVISIBLE);
holder.tvTypeBg.setVisibility(View.INVISIBLE);
holder.tvVoid.setVisibility(View.INVISIBLE);
break;
case Empty:
color = R.color.card_state_empty;
holder.llCardEmpty.setVisibility(View.VISIBLE);
holder.llCardLoaded.setVisibility(View.GONE);
holder.llCardError.setVisibility(View.GONE);
holder.llCardPurged.setVisibility(View.GONE);
holder.imgBlockchain.setVisibility(View.VISIBLE);
if (card.getBlockchain() != null) {
holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol()));
holder.imgBlockchain.setVisibility(View.VISIBLE);
} else {
holder.imgBlockchain.setVisibility(View.INVISIBLE);
}
if (card.useDefaultPIN1() || card.useDefaultPIN2() || card.getPauseBeforePIN2() > 0 || card.useDevelopersFirmware()) {
//holder.imgSecurityNotification.setVisibility(View.VISIBLE);
} else {
holder.imgSecurityNotification.setVisibility(View.INVISIBLE);
}
holder.tvCardID.setText(card.getCIDDescription());
holder.tvCardID.setError(null);
holder.tvCardID.setVisibility(View.VISIBLE);
holder.tvType.setVisibility(View.VISIBLE);
holder.tvTypeBg.setVisibility(View.VISIBLE);
if( card.isReusable() ) {
holder.tvType.setText(" REUSABLE REUSABLE ");
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_wallet, mContext.getTheme()));
}else{
holder.tvType.setText(" BANKNOTE BANKNOTE");
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme()));
}
holder.tvVoid.setVisibility(View.INVISIBLE);
break;
case Purged:
color = R.color.card_state_purged;
holder.llCardEmpty.setVisibility(View.GONE);
holder.llCardLoaded.setVisibility(View.GONE);
holder.llCardError.setVisibility(View.GONE);
holder.llCardPurged.setVisibility(View.VISIBLE);
holder.imgBlockchain.setVisibility(View.VISIBLE);
if (card.getBlockchain() != null) {
holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol()));
holder.imgBlockchain.setVisibility(View.VISIBLE);
} else {
holder.imgBlockchain.setVisibility(View.INVISIBLE);
}
holder.imgSecurityNotification.setVisibility(View.INVISIBLE);
holder.tvCardID.setText(card.getCIDDescription());
holder.tvCardID.setVisibility(View.VISIBLE);
holder.tvCardID.setError(null);
holder.tvType.setVisibility(View.VISIBLE);
holder.tvTypeBg.setVisibility(View.VISIBLE);
holder.tvType.setText(" BANKNOTE BANKNOTE");
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme()));
holder.tvVoid.setVisibility(View.INVISIBLE);
break;
case Loaded:
holder.llCardLoaded.setVisibility(View.VISIBLE);
holder.llCardError.setVisibility(View.GONE);
holder.llCardEmpty.setVisibility(View.GONE);
holder.llCardPurged.setVisibility(View.GONE);
holder.tvCardID.setText(card.getCIDDescription());
holder.tvCardID.setVisibility(View.VISIBLE);
holder.tvWallet.setText(card.getShortWalletString());
holder.tvBlockchain.setText(card.getBlockchainName());
if (card.getBlockchain() != null) {
holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol()));
holder.imgBlockchain.setVisibility(View.VISIBLE);
} else {
holder.imgBlockchain.setVisibility(View.INVISIBLE);
}
if (!engine.HasBalanceInfo(card)) {
color = R.color.card_state_loaded;
} else if (engine.IsBalanceNotZero(card)) {
color = R.color.card_state_loaded_with_coins;
} else {
color = R.color.card_state_loaded_with_zero;
}
if (holder.tvStatusInBlockchain != null) {
if (card.hasBalanceInfo() && card.hasUnspentInfo() && card.getUnspentTransactions().size() > 0) {
holder.tvStatusInBlockchain.setText("Ok");
holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.confirmed, mContext.getTheme()));
} else if (!card.hasBalanceInfo() || !card.hasUnspentInfo()) {
holder.tvStatusInBlockchain.setText("-- -- --");
holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.primary_dark, mContext.getTheme()));
} else {
holder.tvStatusInBlockchain.setText("Not found");
holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.not_confirmed, mContext.getTheme()));
}
}
if ( engine.HasBalanceInfo(card) || card.getOfflineBalance() == null) {
String balance = engine.GetBalance(card);
holder.tvBalance.setText(balance);
holder.tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(card));
holder.tvBalance.setTextColor(Color.BLACK);
holder.tvOffline.setVisibility(View.INVISIBLE);
} else {
String offlineAmount = engine.ConvertByteArrayToAmount(card, card.getOfflineBalance());
holder.tvBalance.setText(engine.GetAmountDescription(card, offlineAmount));
holder.tvBalanceEquivalent.setText(engine.GetAmountEqualentDescriptor(card, offlineAmount));
holder.tvOffline.setVisibility(View.VISIBLE);
}
if (!card.getAmountEquivalentDescriptionAvailable()) {
//holder.tvBalanceEquivalent.setError("Service unavailable");
} else {
holder.tvBalanceEquivalent.setError(null);
}
String error = card.getError();
//holder.tvCardID.setError(error);
if (holder.tvInputs != null) {
holder.tvInputs.setText(card.getInputsDescription());
}
boolean visibleFlag = engine != null ? engine.InOutPutVisible() : true;
int visibleIOPuts = visibleFlag ? View.VISIBLE : View.GONE;
if (holder.tvLastInput != null) {
holder.tvLastInput.setText(card.getLastInputDescription());
if (card.getLastInputDescription().contains("awaiting")) {
holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.not_confirmed, mContext.getTheme()));
} else if (card.getLastInputDescription().contains("None") || card.getLastInputDescription().contains("--")) {
holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.primary_dark, mContext.getTheme()));
} else {
holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.confirmed, mContext.getTheme()));
}
holder.tvLastInput.setVisibility(visibleIOPuts);
}
if (holder.lbLastInput != null) {
holder.lbLastInput.setVisibility(visibleIOPuts);
}
if (holder.tvLastOutput != null) {
holder.tvLastOutput.setText(card.getLastOutputDescription());
holder.tvLastOutput.setVisibility(visibleIOPuts);
}
if (holder.lbLastOutput != null) {
holder.lbLastOutput.setVisibility(visibleIOPuts);
}
if (card.useDefaultPIN1() || card.useDefaultPIN2() || card.getPauseBeforePIN2() > 0 || card.useDevelopersFirmware()) {
//holder.imgSecurityNotification.setVisibility(View.VISIBLE);
} else {
holder.imgSecurityNotification.setVisibility(View.INVISIBLE);
}
holder.tvType.setVisibility(View.VISIBLE);
holder.tvTypeBg.setVisibility(View.VISIBLE);
if( card.isReusable() ) {
holder.tvType.setText(" REUSABLE REUSABLE ");
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_wallet, mContext.getTheme()));
holder.tvVoid.setVisibility(View.INVISIBLE);
}else{
holder.tvType.setText(" BANKNOTE BANKNOTE");
if( card.getRemainingSignatures()!=card.getMaxSignatures() ) {
holder.tvVoid.setVisibility(View.VISIBLE);
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.msg_err, mContext.getTheme()));
}else{
holder.tvVoid.setVisibility(View.INVISIBLE);
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme()));
}
}
break;
}
if(card.useDevelopersFirmware()) {
holder.tvType.setText(" DEVELOPER KIT ");
holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.fab, mContext.getTheme()));
holder.tvVoid.setVisibility(View.INVISIBLE);
}
holder.cv.setOnClickListener(new CardClickListener(position));
holder.cv.setCardBackgroundColor(mContext.getResources().getColor(color));
} catch (Exception e) {
Log.e("onBindViewHolder", e.toString());
}
}
private class CardClickListener implements View.OnClickListener {
Tangem_Card card;
int pos;
CardClickListener(int position) {
card = mCards.get(position);
pos=position;
}
@Override
public void onClick(View v) {
Bundle b = new Bundle();
b.putString("UID", card.getUID());
Bundle bCard = new Bundle();
card.SaveToBundle(bCard);
b.putBundle("Card", bCard);
mUiCallbacks.onViewCard(b);
}
}
}

View file

@ -0,0 +1,83 @@
package com.tangem.wallet;
import android.content.Context;
import android.os.Build;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import static android.text.Html.FROM_HTML_MODE_COMPACT;
/**
* Created by dvol on 17.07.2017.
*/
public class CardUnspentListAdapter extends BaseAdapter {
private LayoutInflater mLayoutInflater;
private Context mContext;
private Tangem_Card mCard;
public CardUnspentListAdapter(LayoutInflater layoutInflater, Tangem_Card card) {
mLayoutInflater = layoutInflater;
mContext = layoutInflater.getContext();
mCard = card;
}
@Override
public int getCount() {
if (mCard != null && mCard.getUnspentTransactions() != null)
return mCard.getUnspentTransactions().size();
return 0;
}
@Override
public Object getItem(int i) {
if (mCard != null && mCard.getUnspentTransactions() != null)
return mCard.getUnspentTransactions().get(i);
return null;
}
@Override
public long getItemId(int i) {
if (mCard != null && mCard.getUnspentTransactions() != null)
return mCard.getUnspentTransactions().get(i).txID.hashCode();
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.card_unspent_list_item, viewGroup,
false);
}
TextView tvItem = (TextView) convertView.findViewById(R.id.tvItem);
Tangem_Card.UnspentTransaction unspentTransaction = (Tangem_Card.UnspentTransaction) getItem(position);
String html=String.format("<b>%d mBTC</b><br>%s", unspentTransaction.Amount, unspentTransaction.txID);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
tvItem.setText(Html.fromHtml(html, FROM_HTML_MODE_COMPACT));
} else {
tvItem.setText(Html.fromHtml(html.toString()));
}
return convertView;
}
public void Clear() {
mCard.getUnspentTransactions().clear();
notifyDataSetChanged();
}
public void UpdateUnspent(String tx_hash, int value, int height) {
Tangem_Card.UnspentTransaction newUT=new Tangem_Card.UnspentTransaction();
newUT.txID=tx_hash;
newUT.Amount=value;
newUT.Height=height;
mCard.getUnspentTransactions().add(newUT);
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,82 @@
package com.tangem.wallet;
import android.net.Uri;
import com.tangem.cardReader.CardProtocol;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
/**
* Created by Ilia on 15.02.2018.
*/
public abstract class CoinEngine {
public abstract String GetNextNode(Tangem_Card mCard);
public abstract int GetNextNodePort(Tangem_Card mCard);
public abstract String GetNode(Tangem_Card mCard);
public abstract int GetNodePort(Tangem_Card mCard);
public abstract void SwitchNode(Tangem_Card mCard);
public abstract boolean AwaitingConfirmation(Tangem_Card card);
public abstract boolean HasBalanceInfo(Tangem_Card card);
public abstract boolean IsBalanceNotZero(Tangem_Card card);
public abstract boolean IsBalanceAlterNotZero(Tangem_Card card);
public abstract boolean CheckAmount(Tangem_Card card, String amount) throws Exception;
public abstract int GetTokenDecimals(Tangem_Card card);
public abstract String GetContractAddress(Tangem_Card card);
public abstract byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception;
public abstract boolean CheckUnspentTransaction(Tangem_Card mCard);
public abstract Uri getShareWalletURIExplorer(Tangem_Card mCard);
public abstract Long GetBalanceLong(Tangem_Card mCard);
public abstract Uri getShareWalletURI(Tangem_Card mCard);
public abstract String EvaluteFeeEquivalent(Tangem_Card mCard, String fee);
public abstract boolean CheckAmountValie(Tangem_Card mCard, String amount, String fee, Long minFeeInInternalUnits);
public abstract boolean InOutPutVisible();
public abstract String GetBalance(Tangem_Card mCard);
public abstract String GetBalanceWithAlter(Tangem_Card mCard);
public abstract String GetBalanceCurrency(Tangem_Card card);
public abstract String GetFeeCurrency();
public abstract boolean IsNeedCheckNode();
public abstract String GetBalanceEquivalent(Tangem_Card mCard);
public abstract String GetBalanceValue(Tangem_Card mCard);
public abstract String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception;
public abstract String GetAmountEqualentDescriptor(Tangem_Card mCard, String value);
public abstract boolean ValdateAddress(String address, Tangem_Card catd);
public abstract String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException;
public abstract String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception;
public abstract byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception;
}

View file

@ -0,0 +1,23 @@
package com.tangem.wallet;
/**
* Created by Ilia on 15.02.2018.
*/
public class CoinEngineFactory {
public static CoinEngine Create(Blockchain chain)
{
if(Blockchain.BitcoinCash == chain || Blockchain.BitcoinCashTestNet == chain) {
return new BtcCashEngine();
}else if(Blockchain.Bitcoin == chain || Blockchain.BitcoinTestNet == chain) {
return new BtcEngine(); //TODO: ВРЕМЕНГГО!!!!
}else if(Blockchain.Ethereum == chain || Blockchain.EthereumTestNet == chain) {
return new EthEngine();
}
else if(Blockchain.Token == chain) {
return new TokenEngine();
} else {
return null;
}
}
}

View file

@ -0,0 +1,732 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.Html;
import android.text.Spanned;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.tangem.cardReader.NfcManager;
import com.tangem.cardReader.Util;
import org.json.JSONException;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class ConfirmPaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback {
private static final int REQUEST_CODE_SIGN_PAYMENT = 1;
private static final int REQUEST_CODE_REQUEST_PIN2 = 2;
Button btnSend;
boolean feeRequestSuccess = false;
boolean balanceRequestSuccess = false;
EditText etWallet;
TextView tvCardID, tvBalance, tvCurrency, tvCurrency2, tvBalanceEquivalent, tvAmountEquivalent, tvFeeEquivalent;
EditText etAmount;
EditText etFee;
ImageView ivCamera;
Tangem_Card mCard;
RadioGroup rgFee;
String minFee = null, maxFee = null, normalFee = null;
Long minFeeInInternalUnits = 0L;
private NfcManager mNfcManager;
int requestPIN2Count = 0;
ProgressBar progressBar;
boolean nodeCheck = false;
Date dtVerifyed=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_confirm_payment);
MainActivity.commonInit(getApplicationContext());
mNfcManager = new NfcManager(this, this);
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card"));
progressBar = findViewById(R.id.progressBar);
btnSend = findViewById(R.id.btnSend);
etWallet = findViewById(R.id.etWallet);
tvCardID = findViewById(R.id.tvCardID);
tvBalance = findViewById(R.id.tvBalance);
tvCurrency = findViewById(R.id.tvCurrency);
tvCurrency2 = findViewById(R.id.tvCurrency2);
etAmount = findViewById(R.id.etAmount);
etFee = findViewById(R.id.etFee);
tvBalanceEquivalent = findViewById(R.id.tvBalanceEquivalent);
tvAmountEquivalent = findViewById(R.id.tvAmountEquivalent);
tvFeeEquivalent = findViewById(R.id.tvFeeEquivalent);
ivCamera = findViewById(R.id.ivCamera);
rgFee = findViewById(R.id.rgFee);
rgFee.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
doSetFee(checkedId);
}
});
etAmount.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
try {
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
tvAmountEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, etAmount.getText().toString()));
if (!mCard.getAmountEquivalentDescriptionAvailable()) {
tvAmountEquivalent.setError("Service unavailable");
} else {
tvAmountEquivalent.setError(null);
}
} catch (Exception e) {
e.printStackTrace();
tvAmountEquivalent.setText("");
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
etFee.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
try {
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
String eqFee = engine.EvaluteFeeEquivalent(mCard, etFee.getText().toString());
tvFeeEquivalent.setText(eqFee);
if (!mCard.getAmountEquivalentDescriptionAvailable()) {
tvFeeEquivalent.setError("Service unavailable");
} else {
tvFeeEquivalent.setError(null);
}
} catch (Exception e) {
e.printStackTrace();
tvFeeEquivalent.setText("");
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
//tvBalance.setText(engine.GetBalanceWithAlter(mCard));
if (mCard.getBlockchain() == Blockchain.Token) {
Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard));
tvBalance.setText(html);
} else {
tvBalance.setText(engine.GetBalanceWithAlter(mCard));
}
etAmount.setText(getIntent().getStringExtra("Amount"));
tvCurrency.setText(engine.GetBalanceCurrency(mCard));
tvCurrency2.setText(engine.GetFeeCurrency());
tvCardID.setText(mCard.getCIDDescription());
//tvBalanceEquivalent.setText(mCard.getBalanceEquivalentDescription());
tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard));
if (!mCard.getAmountEquivalentDescriptionAvailable()) {
tvBalanceEquivalent.setError("Service unavailable");
} else {
tvBalanceEquivalent.setError(null);
}
etWallet.setText(getIntent().getStringExtra("Wallet"));
etFee.setText("?");
btnSend.setVisibility(View.INVISIBLE);
feeRequestSuccess = false;
balanceRequestSuccess = false;
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.MINUTE,-1);
if( dtVerifyed==null || dtVerifyed.before(calendar.getTime()) ) {
FinishActivityWithError(Activity.RESULT_CANCELED, "The obtained data is outdated! Try again");
return;
}
CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain());
if (engineCoin.IsNeedCheckNode() && !nodeCheck) {
Toast.makeText(getBaseContext(), "Cannot reach current active blockchain node. Try again", Toast.LENGTH_LONG).show();
return;
}
String txFee = etFee.getText().toString();
String txAmount = etAmount.getText().toString();
if (!engineCoin.HasBalanceInfo(mCard)) {
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes");
return;
} else if (!engineCoin.IsBalanceNotZero(mCard)) {
FinishActivityWithError(Activity.RESULT_CANCELED, "The wallet is empty");
return;
} else if(!engineCoin.CheckUnspentTransaction(mCard)) {
//else if (mCard.getUnspentTransactions().size() == 0 && mCard.getBlockchain() != Blockchain.Ethereum) {
FinishActivityWithError(Activity.RESULT_CANCELED, "Please wait for confirmation of incoming transaction");
return;
}
if(!engineCoin.CheckAmountValie(mCard, txAmount, txFee, minFeeInInternalUnits))
{
FinishActivityWithError(Activity.RESULT_CANCELED, "Fee exceeds payment amount. Enter correct value and repeat sending.");
return;
}
requestPIN2Count = 0;
Intent intent = new Intent(getBaseContext(), RequestPINActivity.class);
intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString());
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2);
}
});
if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) {
ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain());
Infura_Request req = Infura_Request.GetGasPrise(mCard.getWallet());
req.setID(67);
req.setBlockchain(mCard.getBlockchain());
rgFee.setEnabled(false);
task.execute(req);
} else {
rgFee.setEnabled(true);
SharedData data = new SharedData(SharedData.COUNT_REQUEST);
CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain());
for(int i =0 ; i < data.allRequest; ++i) {
String nodeAddress = engineCoin.GetNextNode(mCard);
int nodePort = engineCoin.GetNextNodePort(mCard);
//ConnectTask connectTaskEx = new ConnectTask(Blockchain.getNextServiceHost(mCard), Blockchain.getNextServicePort(mCard), data);
ConnectTask connectTaskEx = new ConnectTask(nodeAddress, nodePort, data);
//connectTaskEx.execute(Electrum_Request.CheckBalance(mCard.getWallet()));
connectTaskEx.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR,Electrum_Request.CheckBalance(mCard.getWallet()));
}
String nodeAddress = engineCoin.GetNode(mCard);
int nodePort = engineCoin.GetNodePort(mCard);
ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort, data);
//ConnectTask connectTask = new ConnectTask(Blockchain.getServiceHost(mCard), Blockchain.getServicePort(mCard));
connectTask.execute(/*Electrum_Request.CheckBalance(mCard.getWallet()), */Electrum_Request.GetFee(mCard.getWallet()));
int calcSize = 256;
try {
calcSize = BuildSize(etWallet.getText().toString(), "0.00", etAmount.getText().toString());
} catch (Exception ex) {
Log.e("Build Fee error", ex.getMessage());
}
SharedData sharedFee = new SharedData(SharedData.COUNT_REQUEST);
progressBar.setVisibility(View.VISIBLE);
for(int i = 0; i < SharedData.COUNT_REQUEST; ++i)
{
ConnectFeeTask feeTask = new ConnectFeeTask(sharedFee);
feeTask.execute(Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.NORMAL),
Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.MINIMAL),
Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.PRIORITY));
}
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
Intent intent = new Intent();
intent.putExtra("message", "Operation canceled");
setResult(Activity.RESULT_CANCELED, intent);
finish();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SIGN_PAYMENT) {
if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) {
Tangem_Card updatedCard=new Tangem_Card(data.getStringExtra("UID"));
updatedCard.LoadFromBundle(data.getBundleExtra("Card"));
mCard=updatedCard;
}
if (resultCode == SignPaymentActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++;
Intent intent = new Intent(getBaseContext(), RequestPINActivity.class);
intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString());
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2);
return;
}
setResult(resultCode, data);
finish();
} else if (requestCode == REQUEST_CODE_REQUEST_PIN2) {
if (resultCode == Activity.RESULT_OK) {
Intent intent = new Intent(getBaseContext(), SignPaymentActivity.class);
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
intent.putExtra("Wallet", etWallet.getText().toString());
intent.putExtra("Amount", etAmount.getText().toString());
intent.putExtra("Fee", etFee.getText().toString());
startActivityForResult(intent, REQUEST_CODE_SIGN_PAYMENT);
} else {
Toast.makeText(getBaseContext(), "PIN2 is required to sign the payment", Toast.LENGTH_LONG).show();
}
}
}
void FinishActivityWithError(int errorCode, String message) {
//Snackbar.make(etFee, message, Snackbar.LENGTH_LONG).show();
Intent intent = new Intent();
intent.putExtra("message", message);
setResult(errorCode, intent);
finish();
}
@Override
public void onTagDiscovered(Tag tag) {
try {
Log.w(getClass().getName(), "Ignore discovered tag!");
mNfcManager.IgnoreTag(tag);
} catch (IOException e) {
e.printStackTrace();
}
}
int BuildSize(String outputAddress, String outFee, String outAmount) throws Exception {
String myAddress = mCard.getWallet();
String changeAddress = myAddress; //"n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi";
byte[] pbKey = mCard.getWalletPublicKey();
byte[] pbComprKey = mCard.getWalletPublicKeyRar();
// Build script for our address
List<Tangem_Card.UnspentTransaction> rawTxList = mCard.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
fullAmount += unspentOutputs.get(i).value;
}
// Get first unspent
UnspentOutputInfo outPut = unspentOutputs.get(0);
int outPutIndex = outPut.outputIndex;
// get prev TX id;
String prevTXID = rawTxList.get(0).txID;//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2";
long fees = FormatUtil.ConvertStringToLong(outFee);
long amount = FormatUtil.ConvertStringToLong(outAmount);
amount = amount - fees;
long change = fullAmount - fees - amount;
if (amount + fees > fullAmount) {
throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount));
}
byte[][] hashesForSign = new byte[unspentOutputs.size()][];
for (int i = 0; i < unspentOutputs.size(); ++i) {
byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change);
byte[] hashData = Util.calculateSHA256(newTX);
byte[] doubleHashData = Util.calculateSHA256(hashData);
Log.e("TX_BODY_1", BTCUtils.toHex(newTX));
Log.e("TX_HASH_1", BTCUtils.toHex(hashData));
Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData));
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
hashesForSign[i] = doubleHashData;
}
byte[] signFromCard = new byte[64 * unspentOutputs.size()];
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX.length;
}
private class ConnectTask extends Electrum_Task {
public ConnectTask(String host, int port) {
super(host, port);
}
public ConnectTask(String host, int port, SharedData sharedData) {
super(host, port, sharedData);
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(List<Electrum_Request> requests) {
super.onPostExecute(requests);
for (Electrum_Request request : requests) {
try {
if (request.error == null) {
if (request.isMethod(Electrum_Request.METHOD_GetBalance)) {
try {
etFee.setText("--");
//String mWalletAddress = request.getParams().getString(0);
if ((request.getResult().getInt("confirmed") + request.getResult().getInt("unconfirmed")) / mCard.getBlockchain().getMultiplier() * 1000000.0 < Float.parseFloat(etAmount.getText().toString())) {
etFee.setError("Not enough funds");
balanceRequestSuccess = false;
btnSend.setVisibility(View.INVISIBLE);
dtVerifyed=null;
nodeCheck = false;
} else {
etFee.setError(null);
balanceRequestSuccess = true;
if(feeRequestSuccess && balanceRequestSuccess) {
btnSend.setVisibility(View.VISIBLE);
}
dtVerifyed=new Date();
nodeCheck = true;
}
} catch (JSONException e) {
if(sharedCounter != null)
{
int errCounter = sharedCounter.errorRequest.incrementAndGet();
if(errCounter >= sharedCounter.allRequest)
{
e.printStackTrace();
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes");
}
}
else
{
e.printStackTrace();
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes");
}
}
} else if (request.isMethod(Electrum_Request.METHOD_GetFee)) {
if (request.getResultString() == "-1") {
etFee.setText("3");
}
}
} else {
// etFee.setError(request.error);
// btnSend.setVisibility(View.INVISIBLE);
if(sharedCounter != null)
{
int errCounter = sharedCounter.errorRequest.incrementAndGet();
if(errCounter >= sharedCounter.allRequest)
{
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
else
{
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
return;
}
} catch (JSONException e) {
if(sharedCounter != null)
{
int errCounter = sharedCounter.errorRequest.incrementAndGet();
if(errCounter >= sharedCounter.allRequest)
{
e.printStackTrace();
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
else
{
e.printStackTrace();
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
}
}
}
private class ETHRequestTask extends Infura_Task {
ETHRequestTask(Blockchain blockchain){
super(blockchain);
}
@Override
protected void onPostExecute(List<Infura_Request> requests) {
super.onPostExecute(requests);
for (Infura_Request request : requests) {
try {
Long price = 0L;
if (request.error == null) {
if (request.isMethod(Infura_Request.METHOD_ETH_GetGasPrice)) {
try {
String gasPrice = request.getResultString();
gasPrice = gasPrice.substring(2);
BigInteger l = new BigInteger(gasPrice, 16);
BigInteger m = mCard.getBlockchain() == Blockchain.Token ? BigInteger.valueOf(55000) : BigInteger.valueOf(21000);
l = l.multiply(m);
String feeInGwei = mCard.getAmountInGwei(String.valueOf(l));
minFee=feeInGwei;
maxFee=feeInGwei;
normalFee=feeInGwei;
etFee.setText(feeInGwei);
etFee.setError(null);
btnSend.setVisibility(View.VISIBLE);
feeRequestSuccess = true;
balanceRequestSuccess = true;
dtVerifyed=new Date();
minFeeInInternalUnits = mCard.InternalUnitsFromString(feeInGwei);
} catch (JSONException e) {
e.printStackTrace();
FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes");
}
}
} else {
FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes");
}
} catch (JSONException e) {
e.printStackTrace();
FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes");
}
}
}
}
private class ConnectFeeTask extends Fee_Task {
public ConnectFeeTask(SharedData sharedData) {
super(sharedData);
}
@Override
protected void onPostExecute(List<Fee_Request> requests) {
super.onPostExecute(requests);
for (Fee_Request request : requests) {
if (request.error == null) {
long minFeeRate = 0;
try {
try {
String tmpAnswer = request.getAsString();
BigDecimal minFeeBD = new BigDecimal(tmpAnswer);
BigDecimal multiplicator = new BigDecimal("100000000");
minFeeBD = minFeeBD.multiply(multiplicator);
BigInteger minFeeBI = minFeeBD.toBigInteger();
minFeeRate = minFeeBI.longValue();
} catch (Exception e) {
if(sharedCounter != null)
{
int errCounter = sharedCounter.errorRequest.incrementAndGet();
if(errCounter >= sharedCounter.allRequest)
{
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
else
{
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
//FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
return;
}
if (minFeeRate == 0) {
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! Wrong data received from the node");
return;
}
long inputCount = request.txSize;
if (inputCount != 0) {
minFeeRate = minFeeRate * inputCount;
} else {
minFeeRate = minFeeRate * 256;
}
} catch (Exception e) {
e.printStackTrace();
if(sharedCounter != null)
{
int errCounter = sharedCounter.errorRequest.incrementAndGet();
if(errCounter >= sharedCounter.allRequest)
{
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
else
{
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
return;
}
progressBar.setVisibility(View.INVISIBLE);
float finalFee = (float) minFeeRate / (float) 10000;
finalFee = Math.round(finalFee) / (float) 10000;
if (request.getBlockCount() == Fee_Request.MINIMAL) {
minFee = String.valueOf(finalFee);
minFeeInInternalUnits = mCard.InternalUnitsFromString(String.valueOf(finalFee));
} else if (request.getBlockCount() == Fee_Request.NORMAL) {
normalFee = String.valueOf(finalFee);
} else if (request.getBlockCount() == Fee_Request.PRIORITY) {
maxFee = String.valueOf(finalFee);
}
doSetFee(rgFee.getCheckedRadioButtonId());
etFee.setError(null);
feeRequestSuccess = true;
if(feeRequestSuccess && balanceRequestSuccess) {
btnSend.setVisibility(View.VISIBLE);
}
dtVerifyed=new Date();
} else {
if(sharedCounter != null)
{
int errCounter = sharedCounter.errorRequest.incrementAndGet();
if(errCounter >= sharedCounter.allRequest)
{
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
else
{
progressBar.setVisibility(View.INVISIBLE);
FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes");
}
}
}
}
}
private void doSetFee(int checkedRadioButtonId) {
switch (checkedRadioButtonId) {
case R.id.rbMinimalFee:
if (minFee != null) etFee.setText(minFee);
else etFee.setText("?");
break;
case R.id.rbNormalFee:
if (normalFee != null) etFee.setText(normalFee);
else etFee.setText("?");
break;
case R.id.rbMaximumFee:
if (maxFee != null) etFee.setText(maxFee);
else etFee.setText("?");
break;
}
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
super.onPause();
mNfcManager.onPause();
}
@Override
public void onStop() {
super.onStop();
mNfcManager.onStop();
}
}

View file

@ -0,0 +1,330 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.NfcManager;
import com.tangem.cardReader.Util;
public class CreateNewWalletActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications {
public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER;
private Tangem_Card mCard;
private TextView tvCardID;
private NfcManager mNfcManager;
private static final String logTag = "CreateNewActivity";
private ProgressBar progressBar;
private CreateNewWalletTask createNewWalletTask;
private boolean lastReadSuccess = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_new_wallet);
MainActivity.commonInit(getApplicationContext());
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card"));
tvCardID = (TextView) findViewById(R.id.tvCardID);
tvCardID.setText(mCard.getCIDDescription());
mNfcManager = new NfcManager(this, this);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onTagDiscovered(Tag tag) {
try {
// get IsoDep handle and run cardReader thread
final IsoDep isoDep = IsoDep.get(tag);
if (isoDep == null) {
throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err));
}
byte UID[] = tag.getId();
String sUID = Util.byteArrayToHexString(UID);
Log.v(logTag, "UID: " + sUID);
if (sUID.equals(mCard.getUID())) {
if (lastReadSuccess) {
isoDep.setTimeout(mCard.getPauseBeforePIN2() + 5000);
} else {
isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000);
}
createNewWalletTask = new CreateNewWalletTask(isoDep, this);
createNewWalletTask.start();
} else {
Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")");
mNfcManager.IgnoreTag(isoDep.getTag());
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
mNfcManager.onPause();
if (createNewWalletTask != null) {
createNewWalletTask.cancel(true);
}
super.onPause();
}
@Override
public void onStop() {
// dismiss enable NFC dialog
mNfcManager.onStop();
if (createNewWalletTask != null) {
createNewWalletTask.cancel(true);
}
super.onStop();
}
// @Override
// public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) {
// return mNfcManager.ShowNFCEnableDialog(); //onCreateDialog(id, builder, li);
// }
private class CreateNewWalletTask extends Thread {
IsoDep mIsoDep;
CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public CreateNewWalletTask(IsoDep isoDep, CardProtocol.Notifications notifications) {
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(getBaseContext(), 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("CreateNewWalletTask", "[-- Start create new wallet --]");
if (isCancelled) return;
protocol.run_VerifyCard();
Log.i("CreateNewWalletTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName());
mNotifications.OnReadProgress(protocol, 30);
if (isCancelled) return;
// if (mCard.getPauseBeforePIN2() > 0) {
// mNotifications.OnReadWait(mCard.getPauseBeforePIN2());
// }
// try {
protocol.run_CreateWallet(PINStorage.getPIN2());
// } finally {
// mNotifications.OnReadWait(0);
// }
mNotifications.OnReadProgress(protocol, 60);
if (isCancelled) return;
protocol.run_Read();
} finally {
mNfcManager.IgnoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i("CreateNewWalletTask", "[-- Finish create new wallet --]");
mNotifications.OnReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (this.isAlive()) {
isCancelled = true;
join(500);
}
if (this.isAlive() && AllowInterrupt) {
interrupt();
mNotifications.OnReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void OnReadStart(CardProtocol cardProtocol) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(5);
}
});
}
public void OnReadFinish(final CardProtocol cardProtocol) {
createNewWalletTask = null;
if (cardProtocol != null) {
if (cardProtocol.getError() == null) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN));
Intent intent = new Intent();
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
setResult(Activity.RESULT_OK, intent);
finish();
}
});
} else {
lastReadSuccess = false;
if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
Intent intent = new Intent();
intent.putExtra("message", "Cannot create wallet. Make sure you enter correct PIN2!");
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
setResult(RESULT_INVALID_PIN, intent);
finish();
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
return;
} else {
progressBar.post(new Runnable() {
@Override
public void run() {
if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allreadyShowed) {
new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog");
}
} else {
Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show();
}
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
}
}
}
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
public void OnReadProgress(CardProtocol protocol, final int progress) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(progress);
}
});
}
public void OnReadCancel() {
createNewWalletTask = null;
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
@Override
public void OnReadWait(final int msec) {
WaitSecurityDelayDialog.OnReadWait(this, msec);
}
@Override
public void OnReadBeforeRequest(int timeout) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout);
}
@Override
public void OnReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this);
}
}

View file

@ -0,0 +1,270 @@
package com.tangem.wallet;
import android.util.Log;
import com.tangem.cardReader.Util;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.asn1.x9.X9IntegerConverter;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECAlgorithms;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import static org.bitcoinj.core.ECKey.CURVE;
import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER;
/**
* Created by Ilia on 15.02.2018.
*/
public class CryptoUtil {
public static boolean checkHashSign2(byte[] pub, byte[] hash, BigInteger r, BigInteger s)
{
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
signer.init(false, params);
return signer.verifySignature(hash, r, s);
}
public static boolean isCanonical(BigInteger s) {
return s.compareTo(HALF_CURVE_ORDER) <= 0;
}
public static BigInteger toCanonicalised(BigInteger s) {
// The order of the curve is the number of valid points that exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
if(!isCanonical(s)) {
BigInteger canon = CURVE.getN().subtract(s);
Log.e("TX_SIGN", "non Canonical S");
return canon;
}
return s;
}
private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
X9IntegerConverter x9 = new X9IntegerConverter();
byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
return CURVE.getCurve().decodePoint(compEnc);
}
public static byte[] recoverPubBytesFromSignature(int recId, ECDSASignature_ETH sig, byte[] messageHash) {
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
X9ECParameters params = SECNamedCurves.getByName("secp256k1");
ECDomainParameters CURVE2 = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
BigInteger n = CURVE2.getN(); // Curve order.
BigInteger i = BigInteger.valueOf((long) recId / 2);
BigInteger x = sig.r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine
// specified in Section 2.3.7, where mlen = (log2 p)/8 or mlen = m/8.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the
// conversion routine specified in Section 2.3.4. If this conversion routine outputs invalid, then
// do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
ECCurve.Fp curve = (ECCurve.Fp) CURVE2.getCurve();
BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime.
if (x.compareTo(prime) >= 0) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities.
// So it's encoded in the recId.
ECPoint R = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility).
if (!R.multiply(n).isInfinity())
return null;
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
BigInteger e = new BigInteger(1, messageHash);
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation
// ** is point multiplication and + is point addition (the EC group operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive
// inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8.
BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
BigInteger rInv = sig.r.modInverse(n);
BigInteger srInv = rInv.multiply(sig.s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE2.getG(), eInvrInv, R, srInv);
return q.getEncoded(/* compressed */ false);
}
public static byte[] calcSign(byte[] priv, byte[] hash)
{
ECDSASigner signer = new ECDSASigner();
BigInteger d = new BigInteger(priv);
ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE);
signer.init(true, params);
BigInteger[] rs = signer.generateSignature(hash);
byte[] r = rs[0].toByteArray();
byte[] s = rs[1].toByteArray();
byte[] sign = new byte[64];
for(int i = 0; i < 32; ++i)
{
sign[i] = r[i];
sign[i+32] = s[i];
}
return sign;
}
public static boolean isEncodingCanonical(byte[] signature) {
// See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
// Where R and S are not negative (their first byte has its highest bit not set), and not
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
// in which case a single 0 byte is necessary and even required).
if (signature.length < 9 || signature.length > 73)
return false;
int hashType = (signature[signature.length-1] & 0xff) & ~0x80; // mask the byte to prevent sign-extension hurting us
if (hashType < 1 || hashType > 3)
return false;
// "wrong type" "wrong length marker"
if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3)
return false;
int lenR = signature[3] & 0xff;
if (5 + lenR >= signature.length || lenR == 0)
return false;
int lenS = signature[5+lenR] & 0xff;
if (lenR + lenS + 7 != signature.length || lenS == 0)
return false;
// R value type mismatch R value negative
if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80)
return false;
if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80)
return false; // R value excessively padded
// S value type mismatch S value negative
if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80)
return false;
if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80)
return false; // S value excessively padded
return true;
}
public static boolean VerifySign(byte[] tlvPublicKey, byte[] data, byte[] tlvSignature) throws IOException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
Signature signature = Signature.getInstance("SHA256withECDSA");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey);
ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec);
PublicKey publicKey = factory.generatePublic(keySpec);
signature.initVerify(publicKey);
signature.update(data);
ASN1EncodableVector v = new ASN1EncodableVector();
int size = tlvSignature.length / 2;
v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, 0, size))));
v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(tlvSignature, size, size * 2))));
byte[] sigDer = new DERSequence(v).getEncoded();
return signature.verify(sigDer);
}
private static byte[] leaderZero(byte[] s)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (s[0] > 0x7f) {
baos.write((byte) 0x00);
}
for(int i = 0; i < 32; ++i)
baos.write(s[i]);
return baos.toByteArray();
}
public static boolean checkHashSign(byte[] pub, byte[] hash, byte[] sign)
{
byte[] rtmp = new byte[32];
byte[] stmp = new byte[32];
for(int i = 0; i< 32; ++i)
{
rtmp[i] = sign[i];
stmp[i] = sign[i+32];
}
leaderZero(rtmp);
byte[] r2 = leaderZero(rtmp);
byte[] s2 = leaderZero(stmp);
BigInteger r = new BigInteger(r2);
BigInteger s = new BigInteger(s2);
ECDSASigner signer = new ECDSASigner();
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
signer.init(false, params);
return signer.verifySignature(hash, r, s);
}
public static byte[] doubleSha256(byte[] bytes) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
return sha256.digest(sha256.digest(bytes));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] sha256ripemd160(byte[] publicKey) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] sha256hash = sha256.digest(publicKey);
byte[] hashedPublicKey = Util.calculateRIPEMD160(sha256hash);
return hashedPublicKey;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -0,0 +1,130 @@
package com.tangem.wallet;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequenceGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
/**
* Created by Ilia on 15.02.2018.
*/
public class DerEncodingUtil {
public static byte[] PackInteger(byte[] s)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte)0x02);
byte length = (byte)s.length;
if (s[0] > 0x7f) {
baos.write((byte)(length+1));
baos.write((byte) 0x00);
}
else {
baos.write((byte)length);
}
for(int i = 0; i < length; ++i)
baos.write(s[i]);
return baos.toByteArray();
}
public static byte[] packSignDer(BigInteger r, BigInteger s, byte[] pubKey) throws IOException
{
byte[] signDer = DerEncoding(r, s);
BitcoinOutputStream packKey = new BitcoinOutputStream();
packKey.write((byte)0x41);
packKey.write(pubKey);
byte[] keyArray = packKey.toByteArray();
BitcoinOutputStream result = new BitcoinOutputStream();
result.write((byte)(signDer.length+1));
result.write(signDer);
result.write((byte)0x1);
result.write(keyArray);
return result.toByteArray();
}
public static byte[] packSignDerBitcoinCash(BigInteger r, BigInteger s, byte[] pubKey) throws IOException
{
byte[] signDer = DerEncoding(r, s);
BitcoinOutputStream packKey = new BitcoinOutputStream();
packKey.write((byte)0x21); //compress key
packKey.write(pubKey);
byte[] keyArray = packKey.toByteArray();
BitcoinOutputStream result = new BitcoinOutputStream();
result.write((byte)(signDer.length+1));
result.write(signDer);
result.write((byte)0x41);
result.write(keyArray);
return result.toByteArray();
}
public static byte[] DerEncoding(BigInteger r, BigInteger s) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new ASN1Integer(r));
seq.addObject(new ASN1Integer(s));
seq.close();
return bos.toByteArray();
}
public static byte[] DerEncoding(byte[] sign)
{
byte[] r = sign;
byte[] s = new byte[32];
for(int i =0; i < 32; ++i)
{
s[i] = sign[i+32];
}
byte[] newR = PackInteger(r);
byte[] newS = PackInteger(s);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte)(newR.length+newS.length+2));
baos.write((byte)newR.length);
baos.write(newR, 0, newR.length);
baos.write((byte)newS.length);
baos.write(newS, 0, newS.length);
return baos.toByteArray();
}
public static byte[] DerEncodingBI(BigInteger[] sign)
{
byte[] r = sign[0].toByteArray();
byte[] s = sign[1].toByteArray();
byte[] newR = PackInteger(r);
byte[] newS = PackInteger(s);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte)(newR.length+newS.length+2));
baos.write((byte)newR.length);
baos.write(newR, 0, newR.length);
baos.write((byte)newS.length);
baos.write(newS, 0, newS.length);
return baos.toByteArray();
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.wallet;
import android.os.Build;
public class DeviceNFCAntennaLocation {
public float X;
public float Y;
public boolean OnBackSide;
public float Strength;
public void getAntennaLocation() {
String device = DeviceName.getDeviceName();
String model = Build.DEVICE;
this.X = 0.5f;
this.Y = 0.33f;
this.OnBackSide = true;
this.Strength = 1.0f;
// Samsung
// if (model.contains("Samsung")) {this.Y = 0.33; this.Strength = 0.5; }
// if (model.contains("Sony")) {this.Y = 0.33; this.Strength = 0.5; }
// if (device == "Galaxy J5") {this.Y = 0.4; this.Strength = 0.5; }
if (device == "P10 lite") {this.Y = 0.03f; this.Strength = 0.8f; }
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
package com.tangem.wallet;
/**
* Created by Ilia on 18.12.2017.
*/
public interface Digest{
/**
* Insert one more input data byte.
*
* @param in the input byte
*/
void update(byte in);
/**
* Insert some more bytes.
*
* @param inbuf the data bytes
*/
void update(byte[] inbuf);
/**
* Insert some more bytes.
*
* @param inbuf the data buffer
* @param off the data offset in {@code inbuf}
* @param len the data length (in bytes)
*/
void update(byte[] inbuf, int off, int len);
/**
* Finalize the current hash computation and return the hash value
* in a newly-allocated array. The object is resetted.
*
* @return the hash output
*/
byte[] digest();
/**
* Input some bytes, then finalize the current hash computation
* and return the hash value in a newly-allocated array. The object
* is resetted.
*
* @param inbuf the input data
* @return the hash output
*/
byte[] digest(byte[] inbuf);
/**
* Finalize the current hash computation and store the hash value
* in the provided output buffer. The {@code len} parameter
* contains the maximum number of bytes that should be written;
* no more bytes than the natural hash function output length will
* be produced. If {@code len} is smaller than the natural
* hash output length, the hash output is truncated to its first
* {@code len} bytes. The object is resetted.
*
* @param outbuf the output buffer
* @param off the output offset within {@code outbuf}
* @param len the requested hash output length (in bytes)
* @return the number of bytes actually written in {@code outbuf}
*/
int digest(byte[] outbuf, int off, int len);
/**
* Get the natural hash function output length (in bytes).
*
* @return the digest output length (in bytes)
*/
int getDigestLength();
/**
* Reset the object: this makes it suitable for a new hash
* computation. The current computation, if any, is discarded.
*/
void reset();
/**
* Clone the current state. The returned object evolves independantly
* of this object.
*
* @return the clone
*/
Digest copy();
/**
* <p>Return the "block length" for the hash function. This
* value is naturally defined for iterated hash functions
* (Merkle-Damgard). It is used in HMAC (that's what the
* <a href="http://tools.ietf.org/html/rfc2104">HMAC specification</a>
* names the "{@code B}" parameter).</p>
*
* <p>If the function is "block-less" then this function may
* return {@code -n} where {@code n} is an integer such that the
* block length for HMAC ("{@code B}") will be inferred from the
* key length, by selecting the smallest multiple of {@code n}
* which is no smaller than the key length. For instance, for
* the Fugue-xxx hash functions, this function returns -4: the
* virtual block length B is the HMAC key length, rounded up to
* the next multiple of 4.</p>
*
* @return the internal block length (in bytes), or {@code -n}
*/
int getBlockLength();
/**
* <p>Get the display name for this function (e.g. {@code "SHA-1"}
* for SHA-1).</p>
*
* @see Object
*/
String toString();
}

View file

@ -0,0 +1,220 @@
package com.tangem.wallet;
import java.security.MessageDigest;
/**
* Created by Ilia on 18.12.2017.
*/
public abstract class DigestEngine extends MessageDigest implements Digest {
/**
* Reset the hash algorithm state.
*/
protected abstract void engineReset();
/**
* Process one block of data.
*
* @param data the data block
*/
protected abstract void processBlock(byte[] data);
/**
* Perform the final padding and store the result in the
* provided buffer. This method shall call {@link #flush}
* and then {@link #update} with the appropriate padding
* data in order to get the full input data.
*
* @param buf the output buffer
* @param off the output offset
*/
protected abstract void doPadding(byte[] buf, int off);
/**
* This function is called at object creation time; the
* implementation should use it to perform initialization tasks.
* After this method is called, the implementation should be ready
* to process data or meaningfully honour calls such as
* {@link #engineGetDigestLength}
*/
protected abstract void doInit();
private int digestLen, blockLen, inputLen;
private byte[] inputBuf, outputBuf;
private long blockCount;
/**
* Instantiate the engine.
*/
public DigestEngine(String alg)
{
super(alg);
doInit();
digestLen = engineGetDigestLength();
blockLen = getInternalBlockLength();
inputBuf = new byte[blockLen];
outputBuf = new byte[digestLen];
inputLen = 0;
blockCount = 0;
}
private void adjustDigestLen()
{
if (digestLen == 0) {
digestLen = engineGetDigestLength();
outputBuf = new byte[digestLen];
}
}
public byte[] digest()
{
adjustDigestLen();
byte[] result = new byte[digestLen];
digest(result, 0, digestLen);
return result;
}
public byte[] digest(byte[] input)
{
update(input, 0, input.length);
return digest();
}
public int digest(byte[] buf, int offset, int len)
{
adjustDigestLen();
if (len >= digestLen) {
doPadding(buf, offset);
reset();
return digestLen;
} else {
doPadding(outputBuf, 0);
System.arraycopy(outputBuf, 0, buf, offset, len);
reset();
return len;
}
}
public void reset()
{
engineReset();
inputLen = 0;
blockCount = 0;
}
public void update(byte input)
{
inputBuf[inputLen ++] = (byte)input;
if (inputLen == blockLen) {
processBlock(inputBuf);
blockCount ++;
inputLen = 0;
}
}
public void update(byte[] input)
{
update(input, 0, input.length);
}
public void update(byte[] input, int offset, int len)
{
while (len > 0) {
int copyLen = blockLen - inputLen;
if (copyLen > len)
copyLen = len;
System.arraycopy(input, offset, inputBuf, inputLen,
copyLen);
offset += copyLen;
inputLen += copyLen;
len -= copyLen;
if (inputLen == blockLen) {
processBlock(inputBuf);
blockCount ++;
inputLen = 0;
}
}
}
/**
* Get the internal block length. This is the length (in
* bytes) of the array which will be passed as parameter to
* {@link #processBlock}. The default implementation of this
* method calls {@link #getBlockLength} and returns the same
* value. Overriding this method is useful when the advertised
* block length (which is used, for instance, by HMAC) is
* suboptimal with regards to internal buffering needs.
*
* @return the internal block length (in bytes)
*/
protected int getInternalBlockLength()
{
return getBlockLength();
}
/**
* Flush internal buffers, so that less than a block of data
* may at most be upheld.
*
* @return the number of bytes still unprocessed after the flush
*/
protected final int flush()
{
return inputLen;
}
/**
* Get a reference to an internal buffer with the same size
* than a block. The contents of that buffer are defined only
* immediately after a call to {@link #flush()}: if
* {@link #flush()} return the value {@code n}, then the
* first {@code n} bytes of the array returned by this method
* are the {@code n} bytes of input data which are still
* unprocessed. The values of the remaining bytes are
* undefined and may be altered at will.
*
* @return a block-sized internal buffer
*/
protected final byte[] getBlockBuffer()
{
return inputBuf;
}
/**
* Get the "block count": this is the number of times the
* {@link #processBlock} method has been invoked for the
* current hash operation. That counter is incremented
* <em>after</em> the call to {@link #processBlock}.
*
* @return the block count
*/
protected long getBlockCount()
{
return blockCount;
}
/**
* This function copies the internal buffering state to some
* other instance of a class extending {@code DigestEngine}.
* It returns a reference to the copy. This method is intended
* to be called by the implementation of the {@link #copy}
* method.
*
* @param dest the copy
* @return the value {@code dest}
*/
protected Digest copyState(DigestEngine dest)
{
dest.inputLen = inputLen;
dest.blockCount = blockCount;
System.arraycopy(inputBuf, 0, dest.inputBuf, 0,
inputBuf.length);
adjustDigestLen();
dest.adjustDigestLen();
System.arraycopy(outputBuf, 0, dest.outputBuf, 0,
outputBuf.length);
return dest;
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.wallet;
import java.math.BigInteger;
/**
* Created by Ilia on 07.01.2018.
*/
public class ECDSASignature_ETH {
/**
* The two components of the signature.
*/
public final BigInteger r, s;
public byte v;
/**
* Constructs a signature with the given components. Does NOT automatically canonicalise the signature.
*
* @param r -
* @param s -
*/
public ECDSASignature_ETH(BigInteger r, BigInteger s) {
this.r = r;
this.s = s;
}
/**
*t
* @param r
* @param s
* @return -
*/
private static ECDSASignature_ETH fromComponents(byte[] r, byte[] s) {
return new ECDSASignature_ETH(new BigInteger(1, r), new BigInteger(1, s));
}
/**
*
* @param r -
* @param s -
* @param v -
* @return -
*/
public static ECDSASignature_ETH fromComponents(byte[] r, byte[] s, byte v) {
ECDSASignature_ETH signature = fromComponents(r, s);
signature.v = v;
return signature;
}
}

View file

@ -0,0 +1,229 @@
package com.tangem.wallet;
import android.util.Log;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.Arrays;
import static com.tangem.wallet.ByteUtil.EMPTY_BYTE_ARRAY;
/**
* Created by Ilia on 07.01.2018.
*/
public class ETH_Transaction {
byte[] nonce;
byte[] gasPrice;
byte[] gasLimit;
byte[] receiveAddress;
byte[] value;
byte[] data;
Integer chainId;
byte[] rlpRaw;
public ECDSASignature_ETH signature;
byte[] rlpEncoded;
private static final int CHAIN_ID_INC = 35;
private static final int LOWER_REAL_V = 27;
public static ETH_Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice,
BigInteger gasLimit, Integer chainId){
return new ETH_Transaction(BigIntegers.asUnsignedByteArray(nonce),
BigIntegers.asUnsignedByteArray(gasPrice),
BigIntegers.asUnsignedByteArray(gasLimit),
Hex.decode(to),
BigIntegers.asUnsignedByteArray(amount),
null,
chainId);
}
public static ETH_Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice,
BigInteger gasLimit, Integer chainId, byte[] data){
return new ETH_Transaction(BigIntegers.asUnsignedByteArray(nonce),
BigIntegers.asUnsignedByteArray(gasPrice),
BigIntegers.asUnsignedByteArray(gasLimit),
Hex.decode(to),
BigIntegers.asUnsignedByteArray(amount),
data,
chainId);
}
public ETH_Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data,
Integer chainId) {
this.nonce = nonce;
this.gasPrice = gasPrice;
this.gasLimit = gasLimit;
this.receiveAddress = receiveAddress;
if (ByteUtil.isSingleZero(value)) {
this.value = EMPTY_BYTE_ARRAY;
} else {
this.value = value;
}
this.data = data;
this.chainId = chainId;
if (receiveAddress == null) {
this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY;
}
}
public enum ChainEnum
{
Mainnet(1),
Morden(2),
Ropsten(3),
Rinkeby(4),
Rootstock_mainnet(30),
Rootstock_testnet (31),
Kovan (42),
Ethereum_Classic_mainnet(61),
Ethereum_Classic_testnet(62),
Geth_private_chains (1337);
private int value;
ChainEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public byte[] getRawHash() {
byte[] plainMsg = this.getEncodedRaw();
Keccak256 kec = new Keccak256();
return kec.digest(plainMsg);
}
public byte[] getHash() {
byte[] plainMsg = this.getEncoded();
Keccak256 kec = new Keccak256();
return kec.digest(plainMsg);
}
public int BruteRecoveryID2(ECDSASignature_ETH sig, byte[] messageHash, byte[] thisKey)
{
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
int recId = -1;
for (int i = 0; i < 4; i++) {
byte[] recK = CryptoUtil.recoverPubBytesFromSignature(i, sig, messageHash);
if(recK == null)
{
continue;
}
Log.e("ETH_k "+String.valueOf(i), BTCUtils.toHex(recK));
if (Arrays.equals(recK, thisKey)) {
recId = i;
recId +=27;
break;
}
}
return recId;
}
public int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey)
{
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
int recId = -1;
for (int i = 0; i < 4; i++) {
ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, false);
if(k == null)
continue;
byte[] recK = k.getPubKey();
Log.e("ETH_k "+String.valueOf(i), BTCUtils.toHex(recK));
if (k != null && Arrays.equals(recK, thisKey)) {
recId = i;
break;
}
}
return recId;
}
// signed TX
public byte[] getEncoded() {
// parse null as 0 for nonce
byte[] nonce = null;
if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) {
nonce = RLP.encodeElement(null);
} else {
nonce = RLP.encodeElement(this.nonce);
}
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
byte[] value = RLP.encodeElement(this.value);
byte[] data = RLP.encodeElement(this.data);
byte[] v, r, s;
if (signature != null) {
int encodeV;
if (chainId == null) {
encodeV = signature.v;
} else {
encodeV = signature.v - LOWER_REAL_V;
encodeV += chainId * 2 + CHAIN_ID_INC;
}
v = RLP.encodeInt(encodeV);
r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r));
s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s));
} else {
// Since EIP-155 use chainId for v
v = chainId == null ? RLP.encodeElement(EMPTY_BYTE_ARRAY) : RLP.encodeInt(chainId);
r = RLP.encodeElement(EMPTY_BYTE_ARRAY);
s = RLP.encodeElement(EMPTY_BYTE_ARRAY);
}
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit,
receiveAddress, value, data, v, r, s);
//this.hash = this.getHash();
return rlpEncoded;
}
// unsigned TX
public byte[] getEncodedRaw() {
// parse null as 0 for nonce
byte[] nonce = null;
if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) {
nonce = RLP.encodeElement(null);
} else {
nonce = RLP.encodeElement(this.nonce);
}
byte[] gasPrice = RLP.encodeElement(this.gasPrice);
byte[] gasLimit = RLP.encodeElement(this.gasLimit);
byte[] receiveAddress = RLP.encodeElement(this.receiveAddress);
byte[] value = RLP.encodeElement(this.value);
byte[] data = RLP.encodeElement(this.data);
// Since EIP-155 use chainId for v
if (chainId == null) {
rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress,
value, data);
} else {
byte[] v, r, s;
v = RLP.encodeInt(chainId);
r = RLP.encodeElement(EMPTY_BYTE_ARRAY);
s = RLP.encodeElement(EMPTY_BYTE_ARRAY);
rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress,
value, data, v, r, s);
}
return rlpRaw;
}
}

View file

@ -0,0 +1,184 @@
package com.tangem.wallet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by dvol on 16.07.2017.
*/
public class Electrum_Request {
public static final String METHOD_GetBalance = "blockchain.address.get_balance";
public static final String METHOD_ListUnspent = "blockchain.address.listunspent";
public static final String METHOD_GetHistory = "blockchain.address.get_history";
public static final String METHOD_GetTransaction = "blockchain.transaction.get";
public static final String METHOD_GetHeader = "blockchain.block.get_header";
public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast";
public static final String METHOD_GetFee = "blockchain.estimatefee";
public JSONObject jsRequestData;
public String answerData;
public String error;
public String WalletAddress;
public String TxHash;
public String Host;
public int Port;
private Electrum_Request() {
}
public Electrum_Request(JSONObject jsRequest) {
try {
jsRequestData = new JSONObject(jsRequest.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
public JSONObject getAnswer() {
try {
return new JSONObject(answerData);
} catch (Exception e) {
try {
return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage()));
} catch (JSONException e1) {
e1.printStackTrace();
return null;
}
}
}
public String getAsString() {
return jsRequestData.toString();
}
public void setID(int value) {
try {
jsRequestData.put("id", String.format("%d", value));
} catch (JSONException e) {
e.printStackTrace();
}
}
public int getID() {
try {
return jsRequestData.getInt("id");
} catch (JSONException e) {
e.printStackTrace();
return 0;
}
}
public static Electrum_Request CheckBalance(String wallet) {
Electrum_Request request = new Electrum_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetBalance + "\", \"params\":[\"" + wallet + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Electrum_Request GetFee(String wallet) {
Electrum_Request request = new Electrum_Request();
try{
request.WalletAddress = wallet; //METHOD_GetFee
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetFee + "\", \"params\":[\"" + 6 + "\"] }");
}
catch(JSONException e)
{
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Electrum_Request GetHeader(String wallet, String height) {
Electrum_Request request = new Electrum_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetHeader + "\", \"params\":[\"" + height + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Electrum_Request ListUnspent(String wallet) {
Electrum_Request request = new Electrum_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ListUnspent + "\", \"params\":[\"" + wallet + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Electrum_Request Broadcast(String wallet, String tx) {
Electrum_Request request = new Electrum_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_SendTransaction + "\", \"params\":[\"" + tx + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Electrum_Request ListHistory(String wallet) {
Electrum_Request request = new Electrum_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetHistory + "\", \"params\":[\"" + wallet + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Electrum_Request GetTransaction(String wallet, String tx_hash) {
Electrum_Request request = new Electrum_Request();
try {
request.WalletAddress=wallet;
request.TxHash = tx_hash;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetTransaction + "\", \"params\":[\"" + tx_hash + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public boolean isMethod(String methodName) throws JSONException {
return jsRequestData.getString("method").equals(methodName);
}
public JSONArray getParams() throws JSONException {
return jsRequestData.getJSONArray("params");
}
public JSONObject getResult() throws JSONException {
return getAnswer().getJSONObject("result");
}
public String getResultString() throws JSONException {
return getAnswer().getString("result");
}
public JSONArray getResultArray() throws JSONException {
return getAnswer().getJSONArray("result");
}
}

View file

@ -0,0 +1,116 @@
package com.tangem.wallet;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* Created by dvol on 16.07.2017.
*/
public class Electrum_Task extends AsyncTask<Electrum_Request, Integer, List<Electrum_Request>> {
public static final String logTag = "Electrum";
//public static final String Host = /*"hsmiths.changeip.net";*/ "testnetnode.arihanc.com";
//public static final int Port = /*8080*/51001;
private int reqID = 1;
private String Host = "";
private int Port = 0;
OutputStreamWriter out;
BufferedReader in;
SharedData sharedCounter = null;
public Electrum_Task(String host, int port) {
super();
Host = host;
Port = port;
}
public Electrum_Task(String host, int port, SharedData sharedCounter) {
super();
Host = host;
Port = port;
this.sharedCounter = sharedCounter;
}
@Override
protected List<Electrum_Request> doInBackground(Electrum_Request... requests) {
List<Electrum_Request> result = new ArrayList<>();
for (int i = 0; i < requests.length; i++) {
result.add(requests[i]);
}
try {
InetAddress serverAddress = InetAddress.getByName(Host);
Log.v(logTag, "Connecting..."+Host);
Socket socket = new Socket(serverAddress, Port);
socket.setSoTimeout(5000);
try {
OutputStream os = socket.getOutputStream();
out = new OutputStreamWriter(os, "UTF-8");
Log.v(logTag, "Connected");
InputStream is = socket.getInputStream();
in = new BufferedReader(new InputStreamReader(is));
publishProgress(5);
for (int i = 0; i < requests.length; i++) {
requests[i].setID(reqID++);
doRequest(requests[i]);
publishProgress(5 + 90 * (i + 1) / requests.length);
}
publishProgress(100);
} catch (Exception e) {
Log.e(logTag, "Error: ", e);
} finally {
socket.close();
}
} catch (Exception e) {
Log.e(logTag, "Error: ", e);
for (int i = 0; i < requests.length; i++) {
result.get(i).error = e.toString();
}
}
return result;
}
private void doRequest(Electrum_Request request) {
try {
Log.v(logTag, "<< " + request.getAsString());
out.write(request.getAsString() + "\n");
out.flush();
request.answerData = in.readLine();
request.Host=Host;
request.Port=Port;
if (request.answerData != null) {
Log.v(logTag, ">> " + request.answerData);
} else {
request.error = "No answer from server";
Log.v(logTag, ">> <NULL>");
}
} catch (Exception e) {
request.error = e.toString();
}
}
public String getValidationNodeDescription() {
return "Electrum, "+Host+":"+String.valueOf(Port);
}
}

View file

@ -0,0 +1,356 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.NfcManager;
import com.tangem.cardReader.Util;
public class EmptyWalletActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications {
private static final int REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = 2;
private static final int REQUEST_CODE_REQUEST_PIN2 = 3;
private static final int REQUEST_CODE_VERIFY_CARD = 4;
Tangem_Card mCard;
TextView tvCardID, tvIssuer, tvIssuerData, tvBlockchain;
ProgressBar progressBar;
ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion;
private NfcManager mNfcManager;
private final String logTag = "EmptyWalletActivity";
private boolean lastReadSuccess = true;
private VerifyCardTask verifyCardTask = null;
private int requestPIN2Count = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_empty_wallet);
MainActivity.commonInit(getApplicationContext());
mNfcManager = new NfcManager(this, this);
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card"));
tvCardID = findViewById(R.id.tvCardID);
tvCardID.setText(mCard.getCIDDescription());
tvIssuer = findViewById(R.id.tvIssuer);
tvIssuerData = findViewById(R.id.tvIssuerData);
tvBlockchain = findViewById(R.id.tvBlockchain);
tvIssuer.setText(mCard.getIssuerDescription());
tvIssuerData.setText(mCard.getIssuerDataDescription());
//tvBlockchain.setText(mCard.getBlockchain().getOfficialName());
tvBlockchain.setText(mCard.getBlockchainName());
progressBar = findViewById(R.id.progressBar);
ivBlockchain = findViewById(R.id.imgBlockchain);
ivPIN = findViewById(R.id.imgPIN);
ivPIN2orSecurityDelay = findViewById(R.id.imgPIN2orSecurityDelay);
ivDeveloperVersion = findViewById(R.id.imgDeveloperVersion);
ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this, mCard.getTokenSymbol()));
if (mCard.useDefaultPIN1()) {
ivPIN.setImageResource(R.drawable.unlock_pin1);
ivPIN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show();
}
});
} else {
ivPIN.setImageResource(R.drawable.lock_pin1);
ivPIN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show();
}
});
}
if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) {
ivPIN2orSecurityDelay.setImageResource(R.drawable.timer);
ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(EmptyWalletActivity.this, String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show();
}
});
} else if (mCard.useDefaultPIN2()) {
ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2);
ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show();
}
});
} else {
ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2);
ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show();
}
});
}
if (mCard.useDevelopersFirmware()) {
ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version);
ivDeveloperVersion.setVisibility(View.VISIBLE);
ivDeveloperVersion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(EmptyWalletActivity.this, "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show();
}
});
} else {
ivDeveloperVersion.setVisibility(View.INVISIBLE);
}
Button btnNewWallet = findViewById(R.id.btnNewWallet);
btnNewWallet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//CreateSelectBlockchainDialog();
requestPIN2Count = 0;
Intent intent = new Intent(getBaseContext(), RequestPINActivity.class);
intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString());
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2);
}
});
if (getIntent().getExtras().containsKey(NfcAdapter.EXTRA_TAG)) {
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
onTagDiscovered(tag);
}
}
}
private void doCreateNewWallet() {
Intent intent = new Intent(this, CreateNewWalletActivity.class);
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
// intent.putExtra("newPIN",mCard.getPIN());
// intent.putExtra("newPIN2","12345678");
startActivityForResult(intent, REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
data.putExtra("modification", "updateAndViewCard");
data.putExtra("updateDelay", 0);
setResult(Activity.RESULT_OK, data);
}
finish();
} else {
if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) {
Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID"));
updatedCard.LoadFromBundle(data.getBundleExtra("Card"));
mCard = updatedCard;
}
if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++;
Intent intent = new Intent(getBaseContext(), RequestPINActivity.class);
intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString());
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2);
return;
}
}
setResult(resultCode, data);
finish();
} else if (requestCode == REQUEST_CODE_REQUEST_PIN2) {
if (resultCode == Activity.RESULT_OK) {
doCreateNewWallet();
}
}
}
@Override
public void onTagDiscovered(Tag tag) {
try {
final IsoDep isoDep = IsoDep.get(tag);
if (isoDep == null) {
throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err));
}
byte UID[] = tag.getId();
String sUID = Util.byteArrayToHexString(UID);
if (!mCard.getUID().equals(sUID)) {
Log.d(logTag, "Invalid UID: " + sUID);
mNfcManager.IgnoreTag(isoDep.getTag());
return;
} else {
Log.v(logTag, "UID: " + sUID);
}
if (lastReadSuccess) {
isoDep.setTimeout(1000);
} else {
isoDep.setTimeout(65000);
}
//lastTag = tag;
verifyCardTask = new VerifyCardTask(this, mCard, mNfcManager, isoDep, this);
verifyCardTask.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void OnReadStart(CardProtocol cardProtocol) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(5);
}
});
}
public void OnReadFinish(final CardProtocol cardProtocol) {
verifyCardTask = null;
if (cardProtocol != null) {
if (cardProtocol.getError() == null) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN));
Intent intent = new Intent(EmptyWalletActivity.this, VerifyCardActivity.class);
// TODO обновить карту mCard
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
startActivityForResult(intent, REQUEST_CODE_VERIFY_CARD);
//addCard(cardProtocol.getCard());
}
});
} else {
// remove last UIDs because of error and no card read
progressBar.post(new Runnable() {
@Override
public void run() {
lastReadSuccess = false;
if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allreadyShowed) {
new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog");
}
} else {
Toast.makeText(EmptyWalletActivity.this, "Try to scan again", Toast.LENGTH_LONG).show();
}
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
}
}
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
public void OnReadProgress(CardProtocol protocol, final int progress) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(progress);
}
});
}
public void OnReadCancel() {
verifyCardTask = null;
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
@Override
public void OnReadWait(int msec) {
WaitSecurityDelayDialog.OnReadWait(this, msec);
}
@Override
public void OnReadBeforeRequest(int timeout) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout);
}
@Override
public void OnReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this);
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
super.onPause();
mNfcManager.onPause();
}
@Override
public void onStop() {
super.onStop();
mNfcManager.onStop();
}
}

View file

@ -0,0 +1,392 @@
package com.tangem.wallet;
import android.net.Uri;
import android.util.Log;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.TLV;
import org.bitcoinj.core.ECKey;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Date;
import static com.tangem.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class EthEngine extends CoinEngine{
public String GetNextNode(Tangem_Card mCard)
{
return "abc1.hsmiths.com";
}
public int GetNextNodePort(Tangem_Card mCard)
{
return 60001;
}
public String GetNode(Tangem_Card mCard)
{
return "abc1.hsmiths.com";
}
public int GetNodePort(Tangem_Card mCard)
{
return 60001;
}
public void SwitchNode(Tangem_Card mCard)
{
}
public boolean InOutPutVisible()
{
return false;
}
public String GetBalanceCurrency(Tangem_Card card)
{
return "ETH";
}
public boolean AwaitingConfirmation(Tangem_Card card)
{
return false;
}
public String GetFeeCurrency()
{
return "Gwei";
}
public boolean IsNeedCheckNode()
{
return false;
}
BigDecimal convertToEth(String value)
{
BigInteger m = new BigInteger(value, 10);
BigDecimal n = new BigDecimal(m);
BigDecimal d = n.divide(new BigDecimal("1000000000000000000"));
d = d.setScale(8, RoundingMode.DOWN);
return d;
}
public int GetTokenDecimals(Tangem_Card card)
{
return 0;
}
public String GetContractAddress(Tangem_Card card)
{
return "";
}
public boolean ValdateAddress(String address, Tangem_Card card) {
if (address == null || address.isEmpty())
{
return false;
}
if(!address.startsWith("0x")&&!address.startsWith("0X"))
{
return false;
}
if(address.length()!=42)
{
return false;
}
return true;
}
public String GetBalanceValue(Tangem_Card mCard)
{
String dec = mCard.getDecimalBalance();
BigDecimal d = convertToEth(dec);
String s = d.toString();
String pattern = "#0.000"; // If you like 4 zeros
DecimalFormat myFormatter = new DecimalFormat(pattern);
String output = myFormatter.format(d);
return output;
}
public static String getAmountEquivalentDescriptionETH(BigDecimal amount, float rateValue) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0)
return "";
if (rateValue > 0) {
BigDecimal biRate = new BigDecimal(rateValue);
BigDecimal exchangeCurs = biRate.multiply(amount);
exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN);
return "USD " + exchangeCurs.toString();
} else {
return "USD ---";
}
}
public static String getAmountEquivalentDescriptionETH(Double amount, float rate) {
if (amount == 0)
return "";
amount = amount / 100000;
if (rate > 0) {
return String.format("USD%.2f", amount * rate);
} else {
return "USD ---";
}
}
@Override
public String GetBalanceEquivalent(Tangem_Card mCard) {
String dec = mCard.getDecimalBalance();
BigDecimal d = convertToEth(dec);
return getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
@Override
public String GetBalance(Tangem_Card mCard) {
if(!HasBalanceInfo(mCard)){
return "-- -- -- " + GetBalanceCurrency(mCard);
}
String output = GetBalanceValue(mCard);
String s = output + " " + GetBalanceCurrency(mCard);
return s;
}
public Long GetBalanceLong(Tangem_Card mCard)
{
return mCard.getBalance();
}
public String GetBalanceWithAlter(Tangem_Card mCard)
{
return GetBalance(mCard);
}
public boolean IsBalanceAlterNotZero(Tangem_Card card)
{
return true;
}
public boolean IsBalanceNotZero(Tangem_Card card)
{
String balance = card.getDecimalBalance();
if(balance == null || balance == "")
return false;
BigDecimal bi = new BigDecimal(balance);
if (BigDecimal.ZERO.compareTo(bi) == 0)
return false;
return true;
}
@Override
public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception {
throw new Exception("Not implemented");
}
@Override
public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
@Override
public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value)
{
BigDecimal d = new BigDecimal(value);
return getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
public boolean CheckAmount(Tangem_Card card, String amount) throws Exception
{
DecimalFormat decimalFormat = GetDecimalFormat();
BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); //new BigDecimal(strAmount);
BigDecimal maxValue = new BigDecimal(GetBalanceValue(card));
if(amountValue.compareTo(maxValue) > 0 )
{
return false;
}
return true;
}
public boolean HasBalanceInfo(Tangem_Card card)
{
return card.hasBalanceInfo();
}
public Uri getShareWalletURI(Tangem_Card mCard)
{
return Uri.parse("" + mCard.getWallet());
}
public Uri getShareWalletURIExplorer(Tangem_Card mCard)
{
if(mCard.getBlockchain() == Blockchain.EthereumTestNet)
return Uri.parse("https://rinkeby.etherscan.io/address/" + mCard.getWallet());
else
return Uri.parse("https://etherscan.io/address/" + mCard.getWallet());
}
public boolean CheckUnspentTransaction(Tangem_Card mCard)
{
return true;
}
public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits)
{
Long fee = null;
Long amount = null;
try {
amount = mCard.InternalUnitsFromString(amountValue);
fee = mCard.InternalUnitsFromString(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if(fee == null || amount == null)
return false;
if(fee == 0 || amount ==0)
return false;
if(fee < minFeeInInternalUnits)
return false;
BigDecimal tmpFee = new BigDecimal(feeValue);
BigDecimal tmpAmount = new BigDecimal(amountValue);
tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000"));
if (tmpFee.compareTo(tmpAmount) > 0)
return false;
return true;
}
public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee)
{
BigDecimal gweFee = new BigDecimal(fee);
gweFee = gweFee.divide(new BigDecimal("1000000000"));
gweFee = gweFee.setScale(18, RoundingMode.DOWN);
return GetAmountEqualentDescriptor(mCard, gweFee.toString());
}
public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
Keccak256 kec = new Keccak256();
int lenPk = pkUncompressed.length;
if (lenPk < 2) {
throw new IllegalArgumentException("Uncompress public key length is invald");
}
byte[] cleanKey = new byte[lenPk - 1];
for (int i = 0; i < cleanKey.length; ++i) {
cleanKey[i] = pkUncompressed[i + 1];
}
byte[] r = kec.digest(cleanKey);
byte[] address = new byte[20];
for (int i = 0; i < 20; ++i) {
address[i] = r[i + 12];
}
return String.format("0x%s", BTCUtils.toHex(address));
}
public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception {
BigInteger nonceValue = mCard.GetConfirmTXCount();
byte[] pbKey = mCard.getWalletPublicKey();
boolean flag = (mCard.getSigningMethod()== Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = mCard.getIssuer();
BigInteger fee = new BigInteger(feeValue, 10);
BigDecimal amountDec = new BigDecimal(amountValue);
amountDec = amountDec.multiply(new BigDecimal("1000000000"));
BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10);
amount = amount.subtract(fee);
BigInteger nonce = nonceValue;
BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000));
BigInteger gasLimit = BigInteger.valueOf(21000);
Integer chainId = mCard.getBlockchain() == Blockchain.Ethereum ? ETH_Transaction.ChainEnum.Mainnet.getValue() : ETH_Transaction.ChainEnum.Rinkeby.getValue();
Long multiplicator = 1000000000L;
amount = amount.multiply(BigInteger.valueOf(multiplicator));
gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator));
String to = toValue;
if (to.startsWith("0x") || to.startsWith("0X")) {
to = to.substring(2);
}
ETH_Transaction tx = ETH_Transaction.create(to, amount, nonce, gasPrice, gasLimit, chainId);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if(!f)
{
Log.e("ETH-CHECK", "Sign Failed.");
}
tx.signature = new ECDSASignature_ETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
byte[] realTX = tx.getEncoded();
return realTX;
}
}

View file

@ -0,0 +1,92 @@
package com.tangem.wallet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by dvol on 16.07.2017.
*/
public class ExchangeRequest {
public JSONObject jsRequestData;
public String answerData;
public String error;
public String WalletAddress;
public String currency;
public String currencyAlter;
private ExchangeRequest() {
}
public ExchangeRequest(JSONObject jsRequest) {
try {
jsRequestData = new JSONObject(jsRequest.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
public JSONObject getAnswer() {
try {
return new JSONObject(answerData);
} catch (Exception e) {
try {
return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage()));
} catch (JSONException e1) {
e1.printStackTrace();
return null;
}
}
}
public JSONArray getAnswerList() throws JSONException {
return new JSONArray(answerData);
}
public String getAsString() {
return jsRequestData.toString();
}
public void setID(int value) {
try {
jsRequestData.put("id", String.format("%d", value));
} catch (JSONException e) {
e.printStackTrace();
}
}
public int getID() {
try {
return jsRequestData.getInt("id");
} catch (JSONException e) {
e.printStackTrace();
return 0;
}
}
public static ExchangeRequest GetRate(String wallet, String currency, String alterCurrency) {
ExchangeRequest request = new ExchangeRequest();
request.WalletAddress=wallet;
request.currency = currency;
request.currencyAlter = alterCurrency;
return request;
}
public JSONArray getParams() throws JSONException {
return jsRequestData.getJSONArray("params");
}
public JSONObject getResult() throws JSONException {
return getAnswer().getJSONObject("result");
}
public String getResultString() throws JSONException {
return getAnswer().getString("result");
}
public JSONArray getResultArray() throws JSONException {
return getAnswer().getJSONArray("result");
}
}

View file

@ -0,0 +1,66 @@
package com.tangem.wallet;
/**
* Created by Ilia on 16.01.2018.
*/
import android.os.AsyncTask;
import com.tangem.wallet.ExchangeRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Ilia on 04.12.2017.
*/
public class ExchangeTask extends AsyncTask<ExchangeRequest, Void, List<ExchangeRequest>> {
public ExchangeTask()
{
}
protected List<ExchangeRequest> doInBackground(ExchangeRequest... requests) {
List<ExchangeRequest> result = new ArrayList<>();
for (int i = 0; i < requests.length; i++) {
result.add(requests[i]);
}
for (ExchangeRequest request: result)
{
HttpURLConnection httpcon = null;
try {
URL url = new URL("https://api.coinmarketcap.com/v1/ticker/?convert=USD&lmit=10");
httpcon = (HttpURLConnection) url.openConnection();
httpcon.setRequestMethod("GET");
httpcon.connect();
BufferedReader in = new BufferedReader(
new InputStreamReader(httpcon.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
request.answerData = response.toString();
} catch (Exception e) {
request.error = e.getMessage();
} finally {
httpcon.disconnect();
}
}
return result;
}
}

View file

@ -0,0 +1,102 @@
package com.tangem.wallet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by dvol on 16.07.2017.
*/
public class Fee_Request {
public JSONObject jsRequestData;
public String answerData;
public String error;
public String WalletAddress;
public long txSize = 0;
private Fee_Request() {
}
public Fee_Request(JSONObject jsRequest) {
try {
jsRequestData = new JSONObject(jsRequest.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
public JSONObject getAnswer() {
try {
return new JSONObject(answerData);
} catch (Exception e) {
try {
return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage()));
} catch (JSONException e1) {
e1.printStackTrace();
return null;
}
}
}
public String getAsString() {
return answerData;
}
public static final int PRIORITY = 2;
public static final int NORMAL = 3;
public static final int MINIMAL = 6;
private int blockCount = NORMAL;
public void setBlockCount(int count
)
{
blockCount = count;
}
public int getBlockCount()
{
return blockCount;
}
public void setID(int value) {
try {
jsRequestData.put("id", String.format("%d", value));
} catch (JSONException e) {
e.printStackTrace();
}
}
public int getID() {
try {
return jsRequestData.getInt("id");
} catch (JSONException e) {
e.printStackTrace();
return 0;
}
}
public static Fee_Request GetFee(String wallet, long txSize, int blockCount) {
Fee_Request request = new Fee_Request();
request.WalletAddress=wallet;
request.txSize = txSize;
request.setBlockCount(blockCount);
return request;
}
public JSONArray getParams() throws JSONException {
return jsRequestData.getJSONArray("params");
}
public JSONObject getResult() throws JSONException {
return getAnswer().getJSONObject("result");
}
public String getResultString() throws JSONException {
return getAnswer().getString("result");
}
public JSONArray getResultArray() throws JSONException {
return getAnswer().getJSONArray("result");
}
}

View file

@ -0,0 +1,63 @@
package com.tangem.wallet;
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Ilia on 04.12.2017.
*/
public class Fee_Task extends AsyncTask<Fee_Request, Void, List<Fee_Request>> {
SharedData sharedCounter = null;
public Fee_Task(SharedData sharedData)
{
sharedCounter = sharedData;
}
protected List<Fee_Request> doInBackground(Fee_Request... requests) {
List<Fee_Request> result = new ArrayList<>();
for (int i = 0; i < requests.length; i++) {
result.add(requests[i]);
}
for (Fee_Request request: result)
{
HttpURLConnection httpcon = null;
try {
URL url = new URL("https://estimatefee.com/n/"+String.valueOf(request.getBlockCount()));
httpcon = (HttpURLConnection) url.openConnection();
httpcon.setRequestMethod("GET");
httpcon.connect();
BufferedReader in = new BufferedReader(
new InputStreamReader(httpcon.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
request.answerData = response.toString();
} catch (Exception e) {
request.error = e.getMessage();
} finally {
httpcon.disconnect();
}
}
return result;
}
}

View file

@ -0,0 +1,62 @@
package com.tangem.wallet;
import android.annotation.TargetApi;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
/**
* Created by dtaka on 8/20/2016.
*/
@TargetApi(Build.VERSION_CODES.M)
public class FingerprintHelper extends FingerprintManager.AuthenticationCallback {
private FingerprintHelperListener listener;
public FingerprintHelper(FingerprintHelperListener listener) {
this.listener = listener;
}
private CancellationSignal cancellationSignal;
public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) {
cancellationSignal = new CancellationSignal();
try {
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
} catch (SecurityException ex) {
listener.authenticationFailed("An error occurred:\n" + ex.getMessage());
} catch (Exception ex) {
listener.authenticationFailed("An error occurred\n" + ex.getMessage());
}
}
public void cancel() {
if (cancellationSignal != null)
cancellationSignal.cancel();
}
interface FingerprintHelperListener {
public void authenticationFailed(String error);
public void authenticationSucceeded(FingerprintManager.AuthenticationResult result);
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
listener.authenticationFailed("Authentication error\n" + errString);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
listener.authenticationFailed("Authentication help\n" + helpString);
}
@Override
public void onAuthenticationFailed() {
listener.authenticationFailed("Authentication failed.");
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
listener.authenticationSucceeded(result);
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.wallet;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* Created by Ilia on 15.02.2018.
*/
public class FormatUtil {
public static long parseValue(String valueStr) throws NumberFormatException {
return new BigDecimal(valueStr).multiply(BigDecimal.valueOf(1_0000_0000)).setScale(0, BigDecimal.ROUND_HALF_DOWN).longValueExact();
}
public static String DoubleToString(double amount)
{
DecimalFormat myFormatter = GetDecimalFormat();
String output = myFormatter.format(amount);
return output;
}
public static DecimalFormat GetDecimalFormat()
{
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
String pattern = "#0.######";
DecimalFormat myFormatter = new DecimalFormat(pattern, symbols);
myFormatter.setParseBigDecimal(true);
return myFormatter;
}
public static long ConvertStringToLong(String caption) throws Exception {
BigDecimal d = new BigDecimal(caption);
d = d.multiply(new BigDecimal(100000));
d = d.setScale(5);
BigInteger b = d.toBigInteger();
long l = b.longValue();
return l;
}
}

View file

@ -0,0 +1,197 @@
package com.tangem.wallet;
/**
* Created by Ilia on 19.12.2017.
*/
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Infura_Request {
public static final String METHOD_ETH_GetBalance = "eth_getBalance";
public static final String METHOD_ETH_GetOutTransactionCount = "eth_getTransactionCount";
public static final String METHOD_ETH_GetGasPrice = "eth_gasPrice";
public static final String METHOD_ETH_SendRawTransaction = "eth_sendRawTransaction";
public static final String METHOD_ETH_Call = "eth_call";
public JSONObject jsRequestData;
public String answerData;
public String error;
public String WalletAddress;
public int Dec;
public String amount;
public Blockchain blockchain;
public void setBlockchain(Blockchain value) {
blockchain = value;
}
public Blockchain getBlockchain()
{
return blockchain;
}
private Infura_Request() {
}
public Infura_Request(JSONObject jsRequest) {
try {
jsRequestData = new JSONObject(jsRequest.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
public JSONObject getAnswer() {
try {
return new JSONObject(answerData);
} catch (Exception e) {
try {
return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage()));
} catch (JSONException e1) {
e1.printStackTrace();
return null;
}
}
}
public String getAsString() {
return jsRequestData.toString();
}
public void setID(int value) {
try {
jsRequestData.put("id", value/*String.format("%d", value)*/);
} catch (JSONException e) {
e.printStackTrace();
}
}
public int getID() {
try {
return jsRequestData.getInt("id");
} catch (JSONException e) {
e.printStackTrace();
return 0;
}
}
public boolean isMethod(String methodName) throws JSONException {
return jsRequestData.getString("method").equals(methodName);
}
public static Infura_Request GetBalance(String wallet) {
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetBalance + "\", \"params\":[\"" + wallet + "\", \"latest\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Infura_Request GetTokenBalance(String wallet, String contract, int dec)
{
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.Dec = dec;
String address = wallet.substring(2);
String dataValue = String.format("{\"data\": \"0x70a08231000000000000000000000000%s\", \"to\": \"%s\"}", address, contract);
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_Call + "\", \"params\":[" +dataValue+", \"latest\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Infura_Request SendTransaction(String wallet, String tx) {
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_SendRawTransaction + "\", \"params\":[\"" + tx + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Infura_Request GetOutTransactionCount(String wallet) {
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetOutTransactionCount + "\", \"params\":[\"" + wallet + "\", \"latest\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Infura_Request GetPendingTransactionCount(String wallet) {
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetOutTransactionCount + "\", \"params\":[\"" + wallet + "\", \"pending\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Infura_Request GetGasPrise(String wallet) {
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetGasPrice + "\", \"params\":[] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
public static Infura_Request SendTransactionCount(String wallet, String TX) {
Infura_Request request = new Infura_Request();
try {
request.WalletAddress=wallet;
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_SendRawTransaction + "\", \"params\":[\"" + TX + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
request.error = e.toString();
}
return request;
}
//METHOD_ETH_GetOutTransactionCount
public JSONArray getParams() throws JSONException {
return jsRequestData.getJSONArray("params");
}
public JSONObject getResult() throws JSONException {
return getAnswer().getJSONObject("result");
}
public String getResultString() throws JSONException {
return getAnswer().getString("result");
}
public JSONArray getResultArray() throws JSONException {
return getAnswer().getJSONArray("result");
}
}

View file

@ -0,0 +1,134 @@
package com.tangem.wallet;
/**
* Created by Ilia on 19.12.2017.
*/
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.HttpsURLConnection;
/**
* Created by Ilia on 04.12.2017.
*/
public class Infura_Task extends AsyncTask<Infura_Request, Void, List<Infura_Request>> {
private Exception exception;
private Blockchain blockchain;
public Infura_Task(Blockchain blockchainNet)
{
blockchain = blockchainNet;
}
boolean useOurNode = false;
protected List<Infura_Request> doInBackground(Infura_Request... requests) {
List<Infura_Request> result = new ArrayList<>();
for (int i = 0; i < requests.length; i++) {
result.add(requests[i]);
}
for (Infura_Request request: result)
{
HttpURLConnection httpcon = null;
try {
URL url = new URL("https://rinkeby.infura.io/AfWg0tmYEX5Kukn2UkKV");
if(blockchain == Blockchain.Ethereum || blockchain == Blockchain.Token){
if(useOurNode) {
URL tmp = new URL("http://52.230.23.88");
url = new URL(tmp.getProtocol(), tmp.getHost(), 27172, tmp.getFile());
}else
url = new URL("https://mainnet.infura.io/AfWg0tmYEX5Kukn2UkKV");
}
if(useOurNode)
{
httpcon = (HttpURLConnection)url.openConnection();
}
else
{
httpcon = (HttpsURLConnection)url.openConnection();
}
if(httpcon == null)
{
request.error = String.format("Cann't connect to %s", url.getHost());
return result;
}
httpcon.setRequestMethod("POST");
httpcon.setRequestProperty("Content-Type", "application/json");
String params = request.getAsString();
OutputStream os = httpcon.getOutputStream();
if(os == null)
{
request.error = String.format("Cann't recieve data from %s", url.getHost());
return result;
}
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
if(writer == null)
{
request.error = String.format("Cann't send data to %s", url.getHost());
}
writer.write(params);
writer.flush();
writer.close();
os.close();
httpcon.connect();
request.getParams();
System.out.println("code:"+httpcon.getResponseCode());
int code = httpcon.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(httpcon.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
request.answerData = response.toString();
} catch (Exception e) {
this.exception = e;
request.error = e.getMessage();
} finally {
httpcon.disconnect();
}
}
return result;
}
public String getValidationNodeDescription() {
if(blockchain == Blockchain.Ethereum || blockchain == Blockchain.Token)
{
if(useOurNode)
return "52.230.23.88:27172";
else
return "Infura, infura.io";
}
return "Infura, rinkeby.infura.io";
}
}

View file

@ -0,0 +1,126 @@
package com.tangem.wallet;
import com.tangem.cardReader.CardCrypto;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import java.math.BigInteger;
import java.util.Arrays;
import static com.tangem.cardReader.CardCrypto.*;
/**
* 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)
;
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);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
}
private String ID;
private String officialName;
private byte[] privateDataKeyArray;
private byte[] publicDataKeyArray;
private byte[] privateTransactionKeyArray;
private byte[] publicTransactionKeyArray;
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() {
return publicDataKeyArray;
}
public byte[] getPublicTransactionKey() {
return publicTransactionKeyArray;
}
public byte[] getPrivateDataKey() {
return privateDataKeyArray;
}
public byte[] getPrivateTransactionKey() {
return privateTransactionKeyArray;
}
public byte[] getID() {
return ID.getBytes();
}
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;
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.wallet;
/**
* Created by Ilia on 18.12.2017.
*/
public class Keccak256 extends KeccakCore {
/**
* Create the engine.
*/
public Keccak256()
{
super("eth-keccak-256");
}
public Digest copy()
{
return copyState(new Keccak256());
}
public int engineGetDigestLength()
{
return 32;
}
@Override
protected byte[] engineDigest() {
return null;
}
@Override
protected void engineUpdate(byte arg0) {
}
@Override
protected void engineUpdate(byte[] arg0, int arg1, int arg2) {
}
}

View file

@ -0,0 +1,546 @@
package com.tangem.wallet;
/**
* Created by Ilia on 18.12.2017.
*/
abstract class KeccakCore extends DigestEngine{
KeccakCore(String alg)
{
super(alg);
}
private long[] A;
private byte[] tmpOut;
private static final long[] RC = {
0x0000000000000001L, 0x0000000000008082L,
0x800000000000808AL, 0x8000000080008000L,
0x000000000000808BL, 0x0000000080000001L,
0x8000000080008081L, 0x8000000000008009L,
0x000000000000008AL, 0x0000000000000088L,
0x0000000080008009L, 0x000000008000000AL,
0x000000008000808BL, 0x800000000000008BL,
0x8000000000008089L, 0x8000000000008003L,
0x8000000000008002L, 0x8000000000000080L,
0x000000000000800AL, 0x800000008000000AL,
0x8000000080008081L, 0x8000000000008080L,
0x0000000080000001L, 0x8000000080008008L
};
/**
* Encode the 64-bit word {@code val} into the array
* {@code buf} at offset {@code off}, in little-endian
* convention (least significant byte first).
*
* @param val the value to encode
* @param buf the destination buffer
* @param off the destination offset
*/
private static void encodeLELong(long val, byte[] buf, int off)
{
buf[off + 0] = (byte)val;
buf[off + 1] = (byte)(val >>> 8);
buf[off + 2] = (byte)(val >>> 16);
buf[off + 3] = (byte)(val >>> 24);
buf[off + 4] = (byte)(val >>> 32);
buf[off + 5] = (byte)(val >>> 40);
buf[off + 6] = (byte)(val >>> 48);
buf[off + 7] = (byte)(val >>> 56);
}
/**
* Decode a 64-bit little-endian word from the array {@code buf}
* at offset {@code off}.
*
* @param buf the source buffer
* @param off the source offset
* @return the decoded value
*/
private static long decodeLELong(byte[] buf, int off)
{
return (buf[off + 0] & 0xFFL)
| ((buf[off + 1] & 0xFFL) << 8)
| ((buf[off + 2] & 0xFFL) << 16)
| ((buf[off + 3] & 0xFFL) << 24)
| ((buf[off + 4] & 0xFFL) << 32)
| ((buf[off + 5] & 0xFFL) << 40)
| ((buf[off + 6] & 0xFFL) << 48)
| ((buf[off + 7] & 0xFFL) << 56);
}
protected void engineReset()
{
doReset();
}
protected void processBlock(byte[] data)
{
/* Input block */
for (int i = 0; i < data.length; i += 8)
A[i >>> 3] ^= decodeLELong(data, i);
long t0, t1, t2, t3, t4;
long tt0, tt1, tt2, tt3, tt4;
long t, kt;
long c0, c1, c2, c3, c4, bnn;
/*
* Unrolling four rounds kills performance big time
* on Intel x86 Core2, in both 32-bit and 64-bit modes
* (less than 1 MB/s instead of 55 MB/s on x86-64).
* Unrolling two rounds appears to be fine.
*/
for (int j = 0; j < 24; j += 2) {
tt0 = A[ 1] ^ A[ 6];
tt1 = A[11] ^ A[16];
tt0 ^= A[21] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 4] ^ A[ 9];
tt3 = A[14] ^ A[19];
tt0 ^= A[24];
tt2 ^= tt3;
t0 = tt0 ^ tt2;
tt0 = A[ 2] ^ A[ 7];
tt1 = A[12] ^ A[17];
tt0 ^= A[22] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 0] ^ A[ 5];
tt3 = A[10] ^ A[15];
tt0 ^= A[20];
tt2 ^= tt3;
t1 = tt0 ^ tt2;
tt0 = A[ 3] ^ A[ 8];
tt1 = A[13] ^ A[18];
tt0 ^= A[23] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 1] ^ A[ 6];
tt3 = A[11] ^ A[16];
tt0 ^= A[21];
tt2 ^= tt3;
t2 = tt0 ^ tt2;
tt0 = A[ 4] ^ A[ 9];
tt1 = A[14] ^ A[19];
tt0 ^= A[24] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 2] ^ A[ 7];
tt3 = A[12] ^ A[17];
tt0 ^= A[22];
tt2 ^= tt3;
t3 = tt0 ^ tt2;
tt0 = A[ 0] ^ A[ 5];
tt1 = A[10] ^ A[15];
tt0 ^= A[20] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 3] ^ A[ 8];
tt3 = A[13] ^ A[18];
tt0 ^= A[23];
tt2 ^= tt3;
t4 = tt0 ^ tt2;
A[ 0] = A[ 0] ^ t0;
A[ 5] = A[ 5] ^ t0;
A[10] = A[10] ^ t0;
A[15] = A[15] ^ t0;
A[20] = A[20] ^ t0;
A[ 1] = A[ 1] ^ t1;
A[ 6] = A[ 6] ^ t1;
A[11] = A[11] ^ t1;
A[16] = A[16] ^ t1;
A[21] = A[21] ^ t1;
A[ 2] = A[ 2] ^ t2;
A[ 7] = A[ 7] ^ t2;
A[12] = A[12] ^ t2;
A[17] = A[17] ^ t2;
A[22] = A[22] ^ t2;
A[ 3] = A[ 3] ^ t3;
A[ 8] = A[ 8] ^ t3;
A[13] = A[13] ^ t3;
A[18] = A[18] ^ t3;
A[23] = A[23] ^ t3;
A[ 4] = A[ 4] ^ t4;
A[ 9] = A[ 9] ^ t4;
A[14] = A[14] ^ t4;
A[19] = A[19] ^ t4;
A[24] = A[24] ^ t4;
A[ 5] = (A[ 5] << 36) | (A[ 5] >>> (64 - 36));
A[10] = (A[10] << 3) | (A[10] >>> (64 - 3));
A[15] = (A[15] << 41) | (A[15] >>> (64 - 41));
A[20] = (A[20] << 18) | (A[20] >>> (64 - 18));
A[ 1] = (A[ 1] << 1) | (A[ 1] >>> (64 - 1));
A[ 6] = (A[ 6] << 44) | (A[ 6] >>> (64 - 44));
A[11] = (A[11] << 10) | (A[11] >>> (64 - 10));
A[16] = (A[16] << 45) | (A[16] >>> (64 - 45));
A[21] = (A[21] << 2) | (A[21] >>> (64 - 2));
A[ 2] = (A[ 2] << 62) | (A[ 2] >>> (64 - 62));
A[ 7] = (A[ 7] << 6) | (A[ 7] >>> (64 - 6));
A[12] = (A[12] << 43) | (A[12] >>> (64 - 43));
A[17] = (A[17] << 15) | (A[17] >>> (64 - 15));
A[22] = (A[22] << 61) | (A[22] >>> (64 - 61));
A[ 3] = (A[ 3] << 28) | (A[ 3] >>> (64 - 28));
A[ 8] = (A[ 8] << 55) | (A[ 8] >>> (64 - 55));
A[13] = (A[13] << 25) | (A[13] >>> (64 - 25));
A[18] = (A[18] << 21) | (A[18] >>> (64 - 21));
A[23] = (A[23] << 56) | (A[23] >>> (64 - 56));
A[ 4] = (A[ 4] << 27) | (A[ 4] >>> (64 - 27));
A[ 9] = (A[ 9] << 20) | (A[ 9] >>> (64 - 20));
A[14] = (A[14] << 39) | (A[14] >>> (64 - 39));
A[19] = (A[19] << 8) | (A[19] >>> (64 - 8));
A[24] = (A[24] << 14) | (A[24] >>> (64 - 14));
bnn = ~A[12];
kt = A[ 6] | A[12];
c0 = A[ 0] ^ kt;
kt = bnn | A[18];
c1 = A[ 6] ^ kt;
kt = A[18] & A[24];
c2 = A[12] ^ kt;
kt = A[24] | A[ 0];
c3 = A[18] ^ kt;
kt = A[ 0] & A[ 6];
c4 = A[24] ^ kt;
A[ 0] = c0;
A[ 6] = c1;
A[12] = c2;
A[18] = c3;
A[24] = c4;
bnn = ~A[22];
kt = A[ 9] | A[10];
c0 = A[ 3] ^ kt;
kt = A[10] & A[16];
c1 = A[ 9] ^ kt;
kt = A[16] | bnn;
c2 = A[10] ^ kt;
kt = A[22] | A[ 3];
c3 = A[16] ^ kt;
kt = A[ 3] & A[ 9];
c4 = A[22] ^ kt;
A[ 3] = c0;
A[ 9] = c1;
A[10] = c2;
A[16] = c3;
A[22] = c4;
bnn = ~A[19];
kt = A[ 7] | A[13];
c0 = A[ 1] ^ kt;
kt = A[13] & A[19];
c1 = A[ 7] ^ kt;
kt = bnn & A[20];
c2 = A[13] ^ kt;
kt = A[20] | A[ 1];
c3 = bnn ^ kt;
kt = A[ 1] & A[ 7];
c4 = A[20] ^ kt;
A[ 1] = c0;
A[ 7] = c1;
A[13] = c2;
A[19] = c3;
A[20] = c4;
bnn = ~A[17];
kt = A[ 5] & A[11];
c0 = A[ 4] ^ kt;
kt = A[11] | A[17];
c1 = A[ 5] ^ kt;
kt = bnn | A[23];
c2 = A[11] ^ kt;
kt = A[23] & A[ 4];
c3 = bnn ^ kt;
kt = A[ 4] | A[ 5];
c4 = A[23] ^ kt;
A[ 4] = c0;
A[ 5] = c1;
A[11] = c2;
A[17] = c3;
A[23] = c4;
bnn = ~A[ 8];
kt = bnn & A[14];
c0 = A[ 2] ^ kt;
kt = A[14] | A[15];
c1 = bnn ^ kt;
kt = A[15] & A[21];
c2 = A[14] ^ kt;
kt = A[21] | A[ 2];
c3 = A[15] ^ kt;
kt = A[ 2] & A[ 8];
c4 = A[21] ^ kt;
A[ 2] = c0;
A[ 8] = c1;
A[14] = c2;
A[15] = c3;
A[21] = c4;
A[ 0] = A[ 0] ^ RC[j + 0];
tt0 = A[ 6] ^ A[ 9];
tt1 = A[ 7] ^ A[ 5];
tt0 ^= A[ 8] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[24] ^ A[22];
tt3 = A[20] ^ A[23];
tt0 ^= A[21];
tt2 ^= tt3;
t0 = tt0 ^ tt2;
tt0 = A[12] ^ A[10];
tt1 = A[13] ^ A[11];
tt0 ^= A[14] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 0] ^ A[ 3];
tt3 = A[ 1] ^ A[ 4];
tt0 ^= A[ 2];
tt2 ^= tt3;
t1 = tt0 ^ tt2;
tt0 = A[18] ^ A[16];
tt1 = A[19] ^ A[17];
tt0 ^= A[15] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[ 6] ^ A[ 9];
tt3 = A[ 7] ^ A[ 5];
tt0 ^= A[ 8];
tt2 ^= tt3;
t2 = tt0 ^ tt2;
tt0 = A[24] ^ A[22];
tt1 = A[20] ^ A[23];
tt0 ^= A[21] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[12] ^ A[10];
tt3 = A[13] ^ A[11];
tt0 ^= A[14];
tt2 ^= tt3;
t3 = tt0 ^ tt2;
tt0 = A[ 0] ^ A[ 3];
tt1 = A[ 1] ^ A[ 4];
tt0 ^= A[ 2] ^ tt1;
tt0 = (tt0 << 1) | (tt0 >>> 63);
tt2 = A[18] ^ A[16];
tt3 = A[19] ^ A[17];
tt0 ^= A[15];
tt2 ^= tt3;
t4 = tt0 ^ tt2;
A[ 0] = A[ 0] ^ t0;
A[ 3] = A[ 3] ^ t0;
A[ 1] = A[ 1] ^ t0;
A[ 4] = A[ 4] ^ t0;
A[ 2] = A[ 2] ^ t0;
A[ 6] = A[ 6] ^ t1;
A[ 9] = A[ 9] ^ t1;
A[ 7] = A[ 7] ^ t1;
A[ 5] = A[ 5] ^ t1;
A[ 8] = A[ 8] ^ t1;
A[12] = A[12] ^ t2;
A[10] = A[10] ^ t2;
A[13] = A[13] ^ t2;
A[11] = A[11] ^ t2;
A[14] = A[14] ^ t2;
A[18] = A[18] ^ t3;
A[16] = A[16] ^ t3;
A[19] = A[19] ^ t3;
A[17] = A[17] ^ t3;
A[15] = A[15] ^ t3;
A[24] = A[24] ^ t4;
A[22] = A[22] ^ t4;
A[20] = A[20] ^ t4;
A[23] = A[23] ^ t4;
A[21] = A[21] ^ t4;
A[ 3] = (A[ 3] << 36) | (A[ 3] >>> (64 - 36));
A[ 1] = (A[ 1] << 3) | (A[ 1] >>> (64 - 3));
A[ 4] = (A[ 4] << 41) | (A[ 4] >>> (64 - 41));
A[ 2] = (A[ 2] << 18) | (A[ 2] >>> (64 - 18));
A[ 6] = (A[ 6] << 1) | (A[ 6] >>> (64 - 1));
A[ 9] = (A[ 9] << 44) | (A[ 9] >>> (64 - 44));
A[ 7] = (A[ 7] << 10) | (A[ 7] >>> (64 - 10));
A[ 5] = (A[ 5] << 45) | (A[ 5] >>> (64 - 45));
A[ 8] = (A[ 8] << 2) | (A[ 8] >>> (64 - 2));
A[12] = (A[12] << 62) | (A[12] >>> (64 - 62));
A[10] = (A[10] << 6) | (A[10] >>> (64 - 6));
A[13] = (A[13] << 43) | (A[13] >>> (64 - 43));
A[11] = (A[11] << 15) | (A[11] >>> (64 - 15));
A[14] = (A[14] << 61) | (A[14] >>> (64 - 61));
A[18] = (A[18] << 28) | (A[18] >>> (64 - 28));
A[16] = (A[16] << 55) | (A[16] >>> (64 - 55));
A[19] = (A[19] << 25) | (A[19] >>> (64 - 25));
A[17] = (A[17] << 21) | (A[17] >>> (64 - 21));
A[15] = (A[15] << 56) | (A[15] >>> (64 - 56));
A[24] = (A[24] << 27) | (A[24] >>> (64 - 27));
A[22] = (A[22] << 20) | (A[22] >>> (64 - 20));
A[20] = (A[20] << 39) | (A[20] >>> (64 - 39));
A[23] = (A[23] << 8) | (A[23] >>> (64 - 8));
A[21] = (A[21] << 14) | (A[21] >>> (64 - 14));
bnn = ~A[13];
kt = A[ 9] | A[13];
c0 = A[ 0] ^ kt;
kt = bnn | A[17];
c1 = A[ 9] ^ kt;
kt = A[17] & A[21];
c2 = A[13] ^ kt;
kt = A[21] | A[ 0];
c3 = A[17] ^ kt;
kt = A[ 0] & A[ 9];
c4 = A[21] ^ kt;
A[ 0] = c0;
A[ 9] = c1;
A[13] = c2;
A[17] = c3;
A[21] = c4;
bnn = ~A[14];
kt = A[22] | A[ 1];
c0 = A[18] ^ kt;
kt = A[ 1] & A[ 5];
c1 = A[22] ^ kt;
kt = A[ 5] | bnn;
c2 = A[ 1] ^ kt;
kt = A[14] | A[18];
c3 = A[ 5] ^ kt;
kt = A[18] & A[22];
c4 = A[14] ^ kt;
A[18] = c0;
A[22] = c1;
A[ 1] = c2;
A[ 5] = c3;
A[14] = c4;
bnn = ~A[23];
kt = A[10] | A[19];
c0 = A[ 6] ^ kt;
kt = A[19] & A[23];
c1 = A[10] ^ kt;
kt = bnn & A[ 2];
c2 = A[19] ^ kt;
kt = A[ 2] | A[ 6];
c3 = bnn ^ kt;
kt = A[ 6] & A[10];
c4 = A[ 2] ^ kt;
A[ 6] = c0;
A[10] = c1;
A[19] = c2;
A[23] = c3;
A[ 2] = c4;
bnn = ~A[11];
kt = A[ 3] & A[ 7];
c0 = A[24] ^ kt;
kt = A[ 7] | A[11];
c1 = A[ 3] ^ kt;
kt = bnn | A[15];
c2 = A[ 7] ^ kt;
kt = A[15] & A[24];
c3 = bnn ^ kt;
kt = A[24] | A[ 3];
c4 = A[15] ^ kt;
A[24] = c0;
A[ 3] = c1;
A[ 7] = c2;
A[11] = c3;
A[15] = c4;
bnn = ~A[16];
kt = bnn & A[20];
c0 = A[12] ^ kt;
kt = A[20] | A[ 4];
c1 = bnn ^ kt;
kt = A[ 4] & A[ 8];
c2 = A[20] ^ kt;
kt = A[ 8] | A[12];
c3 = A[ 4] ^ kt;
kt = A[12] & A[16];
c4 = A[ 8] ^ kt;
A[12] = c0;
A[16] = c1;
A[20] = c2;
A[ 4] = c3;
A[ 8] = c4;
A[ 0] = A[ 0] ^ RC[j + 1];
t = A[ 5];
A[ 5] = A[18];
A[18] = A[11];
A[11] = A[10];
A[10] = A[ 6];
A[ 6] = A[22];
A[22] = A[20];
A[20] = A[12];
A[12] = A[19];
A[19] = A[15];
A[15] = A[24];
A[24] = A[ 8];
A[ 8] = t;
t = A[ 1];
A[ 1] = A[ 9];
A[ 9] = A[14];
A[14] = A[ 2];
A[ 2] = A[13];
A[13] = A[23];
A[23] = A[ 4];
A[ 4] = A[21];
A[21] = A[16];
A[16] = A[ 3];
A[ 3] = A[17];
A[17] = A[ 7];
A[ 7] = t;
}
}
protected void doPadding(byte[] out, int off)
{
int ptr = flush();
byte[] buf = getBlockBuffer();
if ((ptr + 1) == buf.length) {
buf[ptr] = (byte)0x81;
} else {
buf[ptr] = (byte)0x01;
for (int i = ptr + 1; i < (buf.length - 1); i ++)
buf[i] = 0;
buf[buf.length - 1] = (byte)0x80;
}
processBlock(buf);
A[ 1] = ~A[ 1];
A[ 2] = ~A[ 2];
A[ 8] = ~A[ 8];
A[12] = ~A[12];
A[17] = ~A[17];
A[20] = ~A[20];
int dlen = engineGetDigestLength();
for (int i = 0; i < dlen; i += 8)
encodeLELong(A[i >>> 3], tmpOut, i);
System.arraycopy(tmpOut, 0, out, off, dlen);
}
protected void doInit()
{
A = new long[25];
tmpOut = new byte[(engineGetDigestLength() + 7) & ~7];
doReset();
}
public int getBlockLength()
{
return 200 - 2 * engineGetDigestLength();
}
private final void doReset()
{
for (int i = 0; i < 25; i ++)
A[i] = 0;
A[ 1] = 0xFFFFFFFFFFFFFFFFL;
A[ 2] = 0xFFFFFFFFFFFFFFFFL;
A[ 8] = 0xFFFFFFFFFFFFFFFFL;
A[12] = 0xFFFFFFFFFFFFFFFFL;
A[17] = 0xFFFFFFFFFFFFFFFFL;
A[20] = 0xFFFFFFFFFFFFFFFFL;
}
protected Digest copyState(KeccakCore dst)
{
System.arraycopy(A, 0, dst.A, 0, 25);
return super.copyState(dst);
}
public String toString()
{
return "Keccak-" + (engineGetDigestLength() << 3);
}
}

View file

@ -0,0 +1,146 @@
package com.tangem.wallet;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.lang.reflect.Array;
import java.util.Date;
import java.util.Map;
import java.util.Set;
/**
* Created by dvol on 30.10.2017.
*/
public class LastSignStorage {
private static SharedPreferences sharedPreferences=null;
private static Set<String> cards = new ArraySet<>();
private static Map<String, Date> dates = new ArrayMap<>();
private static Map<String, String> txCol = new ArrayMap<>();
private static Map<String, String> txCompleteCol = new ArrayMap<>();
static void Init(Context context) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
cards=sharedPreferences.getStringSet("LastSign_Cards", cards);
for (int i = 0; i < cards.size(); i++) {
String wallet = cards.toArray()[i].toString();
Date dt = new Date();
dt.setTime(sharedPreferences.getLong("LastSign_" + wallet, 0));
dates.put(wallet, dt);
}
}
public static boolean needInit() {
return sharedPreferences==null;
}
static class CompleteTx
{
public String TX;
public boolean isComplete;
}
public static Map<String, CompleteTx> GetTxList()
{
Set<String > wallets=sharedPreferences.getStringSet("LastSign_Cards", cards);
Map<String, CompleteTx> txList = new ArrayMap<>();
for (int i = 0; i < wallets.size(); i++) {
String wallet = wallets.toArray()[i].toString();
String tx = sharedPreferences.getString("LastSignTX_" + wallet, "");
boolean complete = sharedPreferences.getBoolean("LastSignTXComplete_" + wallet, false);
CompleteTx txComplete = new CompleteTx();
txComplete.isComplete = complete;
txComplete.TX = tx;
txList.put(wallet, txComplete);
}
return txList;
}
public static Date getLastSignDate(String wallet) {
if (dates.containsKey(wallet)) return dates.get(wallet);
return null;
}
public static void setLastSignDate(String wallet, Date date) {
SharedPreferences.Editor editor = sharedPreferences.edit();
if (!cards.contains(wallet)) {
cards.add(wallet);
editor.putStringSet("LastSign_Cards", cards);
}
dates.put(wallet, date);
editor.putLong("LastSign_" + wallet, date.getTime());
editor.apply();
}
public static void setLastTX(String wallet, String tx) {
SharedPreferences.Editor editor = sharedPreferences.edit();
if (!cards.contains(wallet)) {
cards.add(wallet);
editor.putStringSet("LastSign_Cards", cards);
}
editor.putString("LastSignTX_" + wallet, tx);
editor.putBoolean("LastSignTXComplete_" + wallet, false);
editor.apply();
}
public static void setLastMessage(String wallet, String message) {
SharedPreferences.Editor editor = sharedPreferences.edit();
if (!cards.contains(wallet)) {
cards.add(wallet);
editor.putStringSet("LastSign_Cards", cards);
}
editor.putString("LastSignMessage_" + wallet, message);
editor.apply();
}
public static String getLastMessage(String wallet)
{
try {
String msg = sharedPreferences.getString("LastSignMessage_" + wallet, "");
return msg;
}
catch(Exception e)
{
return "";
}
}
public static void setTxWasSend(String wallet)
{
Map<String, CompleteTx> txList = GetTxList();
if(txList.containsKey(wallet))
{
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("LastSignTXComplete_" + wallet, true);
editor.apply();
}
}
public static boolean getNeedTxSend(String wallet)
{
Map<String, CompleteTx> txList = GetTxList();
if(txList.containsKey(wallet))
{
boolean complete = txList.get(wallet).isComplete;
return !complete;
}
return false;
}
public static String getTxForSend(String wallet)
{
Map<String, CompleteTx> txList = GetTxList();
if(txList.containsKey(wallet))
{
return txList.get(wallet).TX;
}
return "";
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class LoadedWalletActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loaded_wallet);
MainActivity.commonInit(getApplicationContext());
if( getIntent().getExtras().containsKey(NfcAdapter.EXTRA_TAG) )
{
Tag tag=getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null ) {
LoadedWalletActivityFragment fragment=(LoadedWalletActivityFragment)(getSupportFragmentManager().findFragmentById(R.id.loaded_wallet_fragment));
fragment.onTagDiscovered(tag);
}
}
}
@Override
public void onBackPressed() {
LoadedWalletActivityFragment loadedWalletActivityFragment=(LoadedWalletActivityFragment) getSupportFragmentManager().findFragmentById(R.id.loaded_wallet_fragment);
Intent data= loadedWalletActivityFragment.prepareResultIntent();
data.putExtra("modification", "update");
setResult(Activity.RESULT_OK, data);
finish();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
package com.tangem.wallet;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
/**
* Created by dvol on 15.02.2018.
*/
public class LogFileProvider extends ContentProvider {
private static final String CLASS_NAME = "LogFileProvider";
// The authority is the symbolic name for the provider class
public static final String AUTHORITY = "com.tangem.wallet.LogFileProvider";
// UriMatcher used to match against incoming requests
private UriMatcher uriMatcher;
@Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add a URI to the matcher which will match against the form
// 'content://it.my.app.LogFileProvider/*'
// and return 1 in the case that the incoming Uri matches this pattern
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
String LOG_TAG = CLASS_NAME+"-oF";
Log.v(LOG_TAG,
"Called with uri: '" + uri + "'." + uri.getLastPathSegment());
// Check incoming Uri against the matcher
switch (uriMatcher.match(uri)) {
// If it returns 1 - then it matches the Uri defined in onCreate
case 1:
// The desired file name is specified by the last segment of the
// path
// E.g.
// 'content://it.my.app.LogFileProvider/Test.txt'
// Take this and build the path to the file
String fileLocation = getContext().getCacheDir() + File.separator
+ uri.getLastPathSegment();
// 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);
return pfd;
// Otherwise unrecognised Uri
default:
Log.v(LOG_TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: "
+ uri.toString());
}
}
// //////////////////////////////////////////////////////////////
// Not supported / used / required for this example
// //////////////////////////////////////////////////////////////
@Override
public int update(Uri uri, ContentValues contentvalues, String s,
String[] as) {
return 0;
}
@Override
public int delete(Uri uri, String s, String[] as) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues contentvalues) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String s, String[] as1,
String s1) {
return null;
}
}

View file

@ -0,0 +1,285 @@
package com.tangem.wallet;
import android.content.Context;
import android.util.Log;
import com.tangem.cardReader.Util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
public class Logger {
public static File collectLogs(Context context) {
File f = new File(context.getCacheDir().getAbsolutePath() + "/Wallet_" + Util.formatDateTimeToFileName(new Date()) + ".log");
try {
if (f.createNewFile()) {
f.setReadable(true);
FileWriter fileWriter = new FileWriter(f, true);
Process process = Runtime.getRuntime().exec("logcat -d -b main -v time");
try {
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bufferedReader = new BufferedReader(isr);
BufferedWriter buf = new BufferedWriter(fileWriter);
buf.append("Tangem Wallet logs");
buf.newLine();
int i = 0;
String line;
while ((line = bufferedReader.readLine()) != null) {
buf.append(line);
buf.newLine();
i++;
}
Log.e("Logger", String.format("%d log lines collected", i));
buf.newLine();
buf.flush();
buf.close();
} finally {
process.destroy();
}
return f;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
//public class Logger {
//
// public static File[] getLastLogFiles() {
// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs");
// if (!path.exists()) {
// return null;
// }
// File[] files = path.listFiles();
// Arrays.sort(files, new Comparator<File>() {
// @Override
// public int compare(File o1, File o2) {
// if (o1.lastModified() < o2.lastModified()) {
// return -1;
// } else if (o1.lastModified() > o2.lastModified()) {
// return 1;
// }
// return 0;
// }
// });
// if (files.length < 5) return files;
// return Arrays.copyOfRange(files, files.length - 5, files.length);
// }
//
// private static File logFile = null;
//
// private static void initLogFile(Context context) {
// try {
// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs");
// if (!path.exists()) {
// path.mkdirs();
// MediaScannerConnection.scanFile(context, new String[]{path.getParentFile().toString()}, null, null);
// }
// logFile = new File(path, String.format("wallet_%s.log", Util.formatDateTimeToFileName(new Date())));
// logFile.createNewFile();
// logFile.setReadable(true);
//
// // initiate media scan and put the new things into the path array to
// // make the scanner aware of the location and the files you want to see
// MediaScannerConnection.scanFile(context, new String[]{logFile.getAbsolutePath()}, null, null);
// } catch (IOException e) {
// e.printStackTrace();
// }
//
// }
//
// public static boolean isCurrent(File f) {
// if (f == null || logFile == null) return false;
// return f.getAbsolutePath().equals(logFile.getAbsolutePath());
// }
//
//
// private static class LogCatThread extends Thread {
// private boolean Terminated;
//
// private static final Object oSync = new Object();
//
// public void Terminate() {
// Terminated = true;
// synchronized (oSync) {
// oSync.notifyAll();
// }
// try {
// join(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// interrupt();
// }
// }
//
// public void collectLogs(Writer out) {
// try {
// Process process = Runtime.getRuntime().exec("logcat -d -b main -v time");
// try {
// InputStream is = process.getInputStream();
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader bufferedReader = new BufferedReader(isr);
// try {
//
// try {
// //BufferedWriter for performance, true to set append to file flag
// BufferedWriter buf = new BufferedWriter(out);
//
// while (!Terminated && isr.ready()) {
// String line = bufferedReader.readLine();
// buf.append(line);
// buf.newLine();
// }
// //Log.i("Logger",String.format("%d lines added",i));
// buf.newLine();
// buf.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//
//
// } catch (Exception e) {
// e.printStackTrace();
// }
//
// } finally {
// process.destroy();
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
//
// }
//
// @Override
// public void run() {
// Process process = null;
// try {
// if (logFile == null) return;
// process = Runtime.getRuntime().exec("logcat -b main -v time");
// try {
// InputStream is = process.getInputStream();
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader bufferedReader = new BufferedReader(isr);
// while (!Terminated) {
// try {
// synchronized (oSync) {
// oSync.wait(1000);
// }
// if (!logFile.exists()) {
// try {
// logFile.createNewFile();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// try {
// //BufferedWriter for performance, true to set append to file flag
// BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
//
// while (isr.ready()) {
// String line = bufferedReader.readLine();
// buf.append(line);
// buf.newLine();
// }
// //Log.i("Logger",String.format("%d lines added",i));
// buf.newLine();
// buf.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//
//
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// } finally {
// process.destroy();
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
//
// static LogCatThread t = new LogCatThread();
//
// public static void StartSaveToFile(Activity activity) {
// try {
// if (logFile != null) return;
//
// verifyStoragePermissions(activity);
// initLogFile(activity.getApplicationContext());
// t.start();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
//
// public static void StopSaveToFile(Context context) {
// final Object oSync = new Object();
// if (t.isAlive()) {
// t.Terminate();
// }
// if (logFile != null) {
// MediaScannerConnection.scanFile(context, new String[]{logFile.getAbsolutePath().toString()}, null, new MediaScannerConnection.OnScanCompletedListener() {
// @Override
// public void onScanCompleted(String path, Uri uri) {
//// synchronized (oSync) {
//// oSync.notifyAll();
//// }
// }
// });
//// try {
//// synchronized (oSync) {
//// oSync.wait(10000);
// logFile = null;
//// }
//// } catch (InterruptedException e) {
//// e.printStackTrace();
//// }
// }
//
// }
//
// // Storage Permissions
// private static final int REQUEST_EXTERNAL_STORAGE = 1;
// private static String[] PERMISSIONS_STORAGE = {
// Manifest.permission.READ_EXTERNAL_STORAGE,
// Manifest.permission.WRITE_EXTERNAL_STORAGE
// };
//
// //Checks if the app has permission to write to device storage
// //If the app does not has permission then the user will be prompted to grant permissions
// public static void verifyStoragePermissions(Activity activity) {
// // Check if we have write permission
// int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
//
// if (permission != PackageManager.PERMISSION_GRANTED) {
// // We don't have permission so prompt the user
// ActivityCompat.requestPermissions(
// activity,
// PERMISSIONS_STORAGE,
// REQUEST_EXTERNAL_STORAGE
// );
// }
// }
//
//}

View file

@ -0,0 +1,68 @@
package com.tangem.wallet;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class LogoActivity extends AppCompatActivity {
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
hide();
}
};
ImageView imgLogo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logo);
imgLogo= (ImageView) findViewById(R.id.imgLogo);
// Set up the user interaction to manually show or hide the system UI.
imgLogo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {hide();
}
});
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
TextView AppVersion = (TextView) findViewById(R.id.AppVersion);
AppVersion.setText("BETA v." + BuildConfig.VERSION_NAME);
if( !getIntent().getBooleanExtra("skipAutoHide",false)) {
delayedHide(1000);
}
}
private void hide() {
Intent intent=new Intent(getBaseContext(),MainActivity.class);
startActivity(intent);
finish();
}
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
//imgLogo.removeCallbacks(mHideRunnable);
imgLogo.postDelayed(mHideRunnable, delayMillis);
}
}

View file

@ -0,0 +1,404 @@
package com.tangem.wallet;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.scottyab.rootbeer.RootBeer;
import com.skyfishjy.library.RippleBackground;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener {
public static final int DIALOG_ENABLE_INTERNET = 1;
private static final int REQUEST_CODE_SEND_EMAIL = 2;
private String logTag = "MainActivity";
public interface OnCardsClean {
void doClean();
}
// public interface OnCreateNFCDialog {
// Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li);
// }
OnCardsClean onCardsClean;
// OnCreateNFCDialog onCreateNFCDialog;
NfcAdapter.ReaderCallback onNFCReaderCallback;
FloatingActionButton fab;
public void setOnCardsClean(OnCardsClean onCardsClean) {
this.onCardsClean = onCardsClean;
}
// public void setOnCreateNFCDialog(OnCreateNFCDialog onCreateNFCDialog) {
// this.onCreateNFCDialog = onCreateNFCDialog;
// }
public void setNfcAdapterReaderCallback(NfcAdapter.ReaderCallback callback) {
this.onNFCReaderCallback = callback;
}
public void showCleanButton() {
findViewById(R.id.tvTapPrompt).setVisibility(View.INVISIBLE);
}
public void hideCleanButton() {
findViewById(R.id.tvTapPrompt).setVisibility(View.VISIBLE);
}
public static class RootFoundDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle("Your Android device is rooted. Security at risk!")
.setCancelable(false)
.setPositiveButton("Got it",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}
)
.create();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RootBeer rootBeer = new RootBeer(this);
if (rootBeer.isRootedWithoutBusyBoxCheck()) {
//we found indication of root
new RootFoundDialog().show(getFragmentManager(), "RootFoundDialog");
}
setContentView(R.layout.activity_main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
commonInit(getApplicationContext());
TextView tvNFCHint = findViewById(R.id.tvNFCHint);
if(tvNFCHint != null)
{
// tvNFCHint.setText("Scan a banknote with your\n" + PhoneUtility.GetPhoneName() + "\nas shown above");
tvNFCHint.setText("Scan a banknote with your\n smartphone as shown above");
}
DeviceNFCAntennaLocation antenna = new DeviceNFCAntennaLocation();
antenna.getAntennaLocation();
final LinearLayout hand = findViewById(R.id.llHand);
final LinearLayout nfc = findViewById(R.id.llNFC);
final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) hand.getLayoutParams();
final RelativeLayout.LayoutParams lp2 = (RelativeLayout.LayoutParams) nfc.getLayoutParams();
final float dp = getResources().getDisplayMetrics().density;
final float lm = dp*(69 + antenna.X * 75);
lp.topMargin = (int) (dp*(-100 + antenna.Y * 250));
lp2.topMargin = (int) (dp*(-125 + antenna.Y * 250));
nfc.setLayoutParams(lp2);
Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
lp.leftMargin = (int)(lm * interpolatedTime);
hand.setLayoutParams(lp);
}
};
a.setDuration(2000); // in ms
a.setInterpolator(new DecelerateInterpolator());
hand.startAnimation(a);
fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showMenu(view);
}
});
MainActivityFragment mainActivityFragment=(MainActivityFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentMain);
if( mainActivityFragment.getCardListAdapter().getItemCount()>0 )
{
showCleanButton();
}else {
hideCleanButton();
}
final RippleBackground rippleBackground=(RippleBackground)findViewById(R.id.imNFC);
rippleBackground.startRippleAnimation();
Intent intent = getIntent();
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null && onNFCReaderCallback != null) {
onNFCReaderCallback.onTagDiscovered(tag);
}
}
}
public static void commonInit(Context context)
{
if( PINStorage.needInit() ) {
PINStorage.Init(context);
}
if( LastSignStorage.needInit() ) {
LastSignStorage.Init(context);
}
}
@Override
protected void onDestroy() {
// Logger.StopSaveToFile(getApplicationContext());
super.onDestroy();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null && onNFCReaderCallback != null) {
onNFCReaderCallback.onTagDiscovered(tag);
}
}
}
@Override
public boolean onKeyDown(int keycode, KeyEvent e) {
switch (keycode) {
case KeyEvent.KEYCODE_MENU:
fab.requestFocus();
showMenu(fab);
return true;
}
return super.onKeyDown(keycode, e);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
if( BuildConfig.DEBUG ) {
for(int i=0; i<menu.size(); i++ ) menu.getItem(i).setVisible(true);
}
return true;
}
public class Compress {
private static final int BUFFER = 2048;
private String[] _files;
private String _zipFile;
Compress(String[] files, String zipFile) {
_files = files;
_zipFile = zipFile;
}
void zip() {
try {
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream(_zipFile);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
byte data[] = new byte[BUFFER];
for (String _file : _files) {
Log.v("Compress", "Adding: " + _file);
FileInputStream fi = new FileInputStream(_file);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(_file.substring(_file.lastIndexOf("/") + 1));
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
File zipFile = null;
private void sendEmail(String subject, String text, File[] filelocations) {
if (zipFile != null) return;
try {
Intent intent = new Intent(Intent.ACTION_SEND)
//.setData(new Uri.Builder().scheme("mailto").build())
.setType("text/plain")
.putExtra(Intent.EXTRA_EMAIL, new String[]{"android@tangem.com"})
.putExtra(Intent.EXTRA_SUBJECT, subject)
.putExtra(Intent.EXTRA_TEXT, text);
if (filelocations != null && filelocations.length > 0) {
String[] fileNames = new String[filelocations.length];
for (int i = 0; i < filelocations.length; i++)
fileNames[i] = filelocations[i].getAbsolutePath();
zipFile = File.createTempFile("tangemLogs", ".zip", filelocations[0].getParentFile());
Compress compress = new Compress(fileNames, zipFile.getAbsolutePath());
compress.zip();
Log.e(logTag, String.format("Send %d bytes zip with logs", zipFile.length()));
Uri attachment = Uri.parse("content://" + LogFileProvider.AUTHORITY + "/"
+ zipFile.getName());
intent.putExtra(Intent.EXTRA_STREAM, attachment);
zipFile.deleteOnExit();
}
List<ResolveInfo> activities = getPackageManager().queryIntentActivities(intent, 0);
boolean isIntentSafe = activities.size() > 0;
if (isIntentSafe) {
startActivity(intent);
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_SEND_EMAIL) {
if (zipFile != null) {
zipFile.delete();
zipFile = null;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
public void showMenu(View v) {
PopupMenu popup = new PopupMenu(this, v);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.menu_main, popup.getMenu());
if( BuildConfig.DEBUG ) {
for(int i=0; i<popup.getMenu().size(); i++ ) popup.getMenu().getItem(i).setVisible(true);
}
popup.setOnMenuItemClickListener(this);
popup.show();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
return onOptionsItemSelected(item);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
switch (id) {
case R.id.sendLogs:
File f = null;
try {
f = Logger.collectLogs(this);
if (f != null) {
Log.e(logTag, String.format("Collect %d log bytes", f.length()));
sendEmail("Logs", PhoneUtility.getDeviceInfo(), new File[]{f});
} else {
Log.e(logTag, "Can't create temporaly log file");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (f != null && f.exists()) {
f.delete();
}
}
return true;
case R.id.managePIN:
showSavePinActivity();
return true;
case R.id.managePIN2:
showSavePin2Activity();
return true;
case R.id.cleanCards:
if (onCardsClean != null) onCardsClean.doClean();
hideCleanButton();
return true;
case R.id.about:
showLogoActivity();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showLogoActivity() {
Intent intent = new Intent(getBaseContext(), LogoActivity.class);
intent.putExtra("skipAutoHide",true);
startActivity(intent);
}
private void showSavePinActivity() {
Intent intent = new Intent(getBaseContext(), SavePINActivity.class);
intent.putExtra("PIN2", false);
startActivity(intent);
}
private void showSavePin2Activity() {
Intent intent = new Intent(getBaseContext(), SavePINActivity.class);
intent.putExtra("PIN2", true);
startActivity(intent);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,126 @@
package com.tangem.wallet;
import android.util.Log;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.util.Arrays;
/**
* Created by dvol on 09.08.2017.
*/
public enum Manufacturer {
// Unknown("", "Unknown", new byte[]{}),
// SMARTCASH_AG("SMART CASH AG","SMART CASH AG",
// new byte[]{0x04,
// (byte) 0x4F, (byte) 0x53, (byte) 0x90, (byte) 0x2D, (byte) 0x50, (byte) 0xE2, (byte) 0xBB, (byte) 0x16,
// (byte) 0xD3, (byte) 0xDD, (byte) 0xC7, (byte) 0xA2, (byte) 0x03, (byte) 0x97, (byte) 0x28, (byte) 0x5E,
// (byte) 0x94, (byte) 0x21, (byte) 0x53, (byte) 0x69, (byte) 0x59, (byte) 0x8C, (byte) 0xE4, (byte) 0xDD,
// (byte) 0x62, (byte) 0x42, (byte) 0xDD, (byte) 0xB4, (byte) 0x5B, (byte) 0x96, (byte) 0xA1, (byte) 0x03,
// (byte) 0x1A, (byte) 0xF5, (byte) 0xC9, (byte) 0x73, (byte) 0x94, (byte) 0xC6, (byte) 0xF9, (byte) 0xC8,
// (byte) 0xD7, (byte) 0x6F, (byte) 0x38, (byte) 0xF9, (byte) 0x65, (byte) 0xCB, (byte) 0xA8, (byte) 0xAE,
// (byte) 0x85, (byte) 0xAF, (byte) 0xF7, (byte) 0x68, (byte) 0x55, (byte) 0xDC, (byte) 0xAA, (byte) 0x08,
// (byte) 0xF3, (byte) 0xCD, (byte) 0x15, (byte) 0x43, (byte) 0x04, (byte) 0x19, (byte) 0xF4, (byte) 0x49}),
// DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG","SMART CASH AG (DEVELOPERS)",
// new byte[]{0x04,
// (byte) 0xBA, (byte) 0xB8, (byte) 0x6D, (byte) 0x56, (byte) 0x29, (byte) 0x8C, (byte) 0x99, (byte) 0x6F,
// (byte) 0x56, (byte) 0x4A, (byte) 0x84, (byte) 0xFC, (byte) 0x88, (byte) 0xE2, (byte) 0x8A, (byte) 0xED,
// (byte) 0x38, (byte) 0x18, (byte) 0x4B, (byte) 0x12, (byte) 0xF0, (byte) 0x7E, (byte) 0x51, (byte) 0x91,
// (byte) 0x13, (byte) 0xBE, (byte) 0xF4, (byte) 0x8C, (byte) 0x76, (byte) 0xF3, (byte) 0xDF, (byte) 0x3A,
//
// (byte) 0xDC, (byte) 0x30, (byte) 0x35, (byte) 0x99, (byte) 0xB0, (byte) 0x8A, (byte) 0xC0, (byte) 0x5B,
// (byte) 0x55, (byte) 0xEC, (byte) 0x3D, (byte) 0xF9, (byte) 0x8D, (byte) 0x93, (byte) 0x38, (byte) 0x57,
// (byte) 0x3A, (byte) 0x62, (byte) 0x42, (byte) 0xF7, (byte) 0x6F, (byte) 0x5D, (byte) 0x28, (byte) 0xF4,
// (byte) 0xF0, (byte) 0xF3, (byte) 0x64, (byte) 0xE8, (byte) 0x7E, (byte) 0x8F, (byte) 0xCA, (byte) 0x2F});
Unknown("", "Unknown"),
SMARTCASH_AG("SMART CASH AG", "SMART CASH AG"),
DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG", "SMART CASH AG (DEVELOPERS)"),
SMARTCASH("SMART CASH", "SMART CASH");
private String ID;
private String officialName;
// private byte[] publicKey;
// Manufacturer(String id, String officialName, byte[] publicKey) {
Manufacturer(String id, String officialName) {
this.ID = id;
this.officialName = officialName;
// this.publicKey = publicKey;
}
public String getOfficialName() {
return officialName;
}
// public boolean VerifySignature(byte[] Challenge, byte[] Salt, byte[] Signature) {
//
// if (publicKey == null || Challenge == null || Salt == null || Signature == null) {
// Log.e(getOfficialName(), "Not all data read, can't check signature!");
// return false;
// }
// try {
// java.security.Signature signature = java.security.Signature.getInstance("SHA256withECDSA");
// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
// KeyFactory factory = KeyFactory.getInstance("EC", "SC");
//
// ECPoint p1 = spec.getCurve().decodePoint(publicKey);
//
// ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec);
//
// PublicKey publicKey = factory.generatePublic(keySpec);
// signature.initVerify(publicKey);
// signature.update(Challenge);
// signature.update(Salt);
//
// ASN1EncodableVector v = new ASN1EncodableVector();
// int size = Signature.length / 2;
// v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(Signature, 0, size))));
// v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(Signature, size, size * 2))));
// byte[] sigDer = new DERSequence(v).getEncoded();
//
// if (signature.verify(sigDer)) {
// Log.i(getOfficialName(), "Signature verification OK");
// return true;
// } else {
// Log.e(getOfficialName(), "Signature verification failed");
// }
// }
// catch (Exception e)
// {
// e.printStackTrace();
// }
// return false;
// }
//
// public static Manufacturer FindManufacturer(String ID, byte[] Challenge, byte[] Salt, byte[] Signature) {
// Manufacturer[] manufacturers = Manufacturer.values();
// for (int i = 1; i < manufacturers.length; i++) {
// if ( manufacturers[i].ID.equals(ID) && manufacturers[i].VerifySignature(Challenge, Salt, Signature)) {
// return manufacturers[i];
// }
// }
// return Manufacturer.Unknown;
// }
public static Manufacturer FindManufacturer(String ID) {
Manufacturer[] manufacturers = Manufacturer.values();
for (int i = 1; i < manufacturers.length; i++) {
if (manufacturers[i].ID.equals(ID)) {
return manufacturers[i];
}
}
return Manufacturer.Unknown;
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.wallet;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
public class NoExtendedLengthSupportDialog extends DialogFragment {
public static boolean allreadyShowed=false;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle("Warning")
.setMessage("The NFC adapter of the device does not support extended length APDU, it's possible that some functions will not work!")
.setPositiveButton("Got it",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
NoExtendedLengthSupportDialog.allreadyShowed=true;
}
}
)
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
}
}

View file

@ -0,0 +1,206 @@
package com.tangem.wallet;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import com.tangem.cardReader.CardProtocol;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
/**
* Created by dvol on 12.09.2017.
* Global PIN Storage
*/
class PINStorage {
private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2;
private static SharedPreferences sharedPreferences=null;
static void Init(Context context) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
mSavedPIN = sharedPreferences.getString("SavedPIN", null);
mUserPIN = null;
mLastUsedPIN = null;
mEncryptedPIN = null;
mPIN2 = null;
}
static List<String> getPINs() {
ArrayList<String> result = new ArrayList<>();
if (mLastUsedPIN != null) result.add(mLastUsedPIN);
if (mEncryptedPIN != null && !result.contains(mEncryptedPIN)) result.add(mEncryptedPIN);
if (mUserPIN != null && !result.contains(mUserPIN)) result.add(mUserPIN);
if (mSavedPIN != null && !result.contains(mSavedPIN)) result.add(mSavedPIN);
if (!result.contains(CardProtocol.DefaultPIN)) result.add(CardProtocol.DefaultPIN);
return result;
}
static void setLastUsedPIN(String PIN) {
mLastUsedPIN = PIN;
}
static void setUserPIN(String PIN) {
mUserPIN = PIN;
}
static void setPIN2(String PIN) {
mPIN2 = PIN;
}
static void savePIN(String PIN) {
mSavedPIN = PIN;
if (mSavedPIN != null && !mSavedPIN.isEmpty()) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("SavedPIN", mSavedPIN);
editor.apply();
} else {
deletePIN();
}
}
static void deletePIN() {
SharedPreferences.Editor editor = sharedPreferences.edit();
if (mSavedPIN != null && mLastUsedPIN != null && mSavedPIN.equals(mLastUsedPIN)) {
mLastUsedPIN = null;
}
mSavedPIN = null;
editor.remove("SavedPIN");
editor.apply();
}
static void saveEncryptedPIN(Cipher cipher, String PIN) {
try {
byte[] iv = cipher.getIV();
byte[] bytes = cipher.doFinal(PIN.getBytes());
String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP);
String sIV = Base64.encodeToString(iv, Base64.NO_WRAP);
// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV));
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("EncryptedPIN", encryptedPIN);
editor.putString("EncryptedIV", sIV);
editor.apply();
} catch (Exception e) {
e.printStackTrace();
}
}
static byte[] loadEncryptedIV() {
String sIV = sharedPreferences.getString("EncryptedIV", "");
// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV));
return Base64.decode(sIV, Base64.NO_WRAP);
}
static String loadEncryptedPIN(Cipher cipher) {
String encryptedPIN = sharedPreferences.getString("EncryptedPIN", null);
try {
byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP);
mEncryptedPIN = new String(cipher.doFinal(bytes));
// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN));
} catch (Exception e) {
e.printStackTrace();
mEncryptedPIN = null;
}
return mEncryptedPIN;
}
static boolean haveEncryptedPIN() {
return sharedPreferences.getString("EncryptedPIN", null) != null;
}
static void deleteEncryptedPIN() {
if (mEncryptedPIN != null && mLastUsedPIN != null && mEncryptedPIN.equals(mLastUsedPIN)) {
mLastUsedPIN = null;
}
mEncryptedPIN = null;
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove("EncryptedPIN");
editor.remove("EncryptedIV");
editor.apply();
}
static void saveEncryptedPIN2(Cipher cipher, String PIN) {
try {
byte[] iv = cipher.getIV();
byte[] bytes = cipher.doFinal(PIN.getBytes());
String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP);
String sIV = Base64.encodeToString(iv, Base64.NO_WRAP);
// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV));
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("EncryptedPIN2", encryptedPIN);
editor.putString("EncryptedIV2", sIV);
editor.apply();
} catch (Exception e) {
e.printStackTrace();
}
}
static byte[] loadEncryptedIV2() {
String sIV = sharedPreferences.getString("EncryptedIV2", "");
// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV));
return Base64.decode(sIV, Base64.NO_WRAP);
}
static String loadEncryptedPIN2(Cipher cipher) {
String encryptedPIN = sharedPreferences.getString("EncryptedPIN2", null);
try {
byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP);
mPIN2 = new String(cipher.doFinal(bytes));
// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN));
} catch (Exception e) {
e.printStackTrace();
mPIN2 = null;
}
return mPIN2;
}
static boolean haveEncryptedPIN2() {
return sharedPreferences.getString("EncryptedPIN2", null) != null;
}
static void deleteEncryptedPIN2() {
mPIN2 = null;
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove("EncryptedPIN2");
editor.remove("EncryptedIV2");
editor.apply();
}
public static String getPIN2() {
return mPIN2;
}
public static boolean isDefaultPIN(String pin) {
return (pin != null) && (CardProtocol.DefaultPIN.equals(pin));
}
public static boolean isDefaultPIN2(String pin2) {
return (pin2 != null) && (CardProtocol.DefaultPIN2.equals(pin2));
}
public static String getDefaultPIN() {
return CardProtocol.DefaultPIN;
}
public static String getDefaultPIN2() {
return CardProtocol.DefaultPIN2;
}
public static boolean needInit() {
return sharedPreferences==null;
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.wallet;
import android.os.Build;
/**
* Created by Ilia on 20.04.2018.
*/
public class PhoneUtility {
public static String GetPhoneName()
{
return DeviceName.getDeviceName();
}
public static String getDeviceInfo() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("----------------------------------------\n");
stringBuilder.append("MODEL: ").append(Build.MODEL).append("\n");
stringBuilder.append("ID: ").append(Build.ID).append("\n");
stringBuilder.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n");
stringBuilder.append("Brand: ").append(Build.BRAND).append("\n");
stringBuilder.append("Hardware: ").append(Build.HARDWARE).append("\n");
stringBuilder.append("Version: ").append(Build.VERSION.RELEASE).append(", ").append(Build.VERSION.INCREMENTAL).append("\n");
stringBuilder.append("OS: ").append(Build.VERSION.BASE_OS).append("\n");
stringBuilder.append("SDK: ").append(Build.VERSION.SDK_INT).append("\n");
stringBuilder.append("BOARD: ").append(Build.BOARD).append("\n");
stringBuilder.append("FINGERPRINT: ").append(Build.FINGERPRINT).append("\n");
stringBuilder.append("----------------------------------------\n");
return stringBuilder.toString();
}
}

View file

@ -0,0 +1,237 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.Html;
import android.text.Spanned;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.tangem.cardReader.NfcManager;
import java.io.IOException;
public class PreparePaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback {
private static final int REQUEST_CODE_SCAN_QR = 1;
private static final int REQUEST_CODE_SEND_PAYMENT = 2;
Button btnVerify;
EditText etWallet;
EditText etAmount;
TextView tvCurrency;
TextView tvCardId, tvBalance, tvBalanceEquivalent, tvAmountEquivalent;
ImageView ivCamera;
boolean use_mCurrency;
Tangem_Card mCard;
private NfcManager mNfcManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_prepare_payment);
MainActivity.commonInit(getApplicationContext());
mNfcManager = new NfcManager(this, this);
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card"));
btnVerify = (Button) findViewById(R.id.btnVerify);
etWallet = (EditText) findViewById(R.id.etWallet);
etAmount = (EditText) findViewById(R.id.etAmount);
ivCamera = (ImageView) findViewById(R.id.ivCamera);
tvCurrency = (TextView) findViewById(R.id.tvCurrency);
tvCardId = (TextView) findViewById(R.id.tvCardID);
tvBalance = (TextView) findViewById(R.id.tvBalance);
tvBalanceEquivalent = (TextView) findViewById(R.id.tvBalanceEquivalent);
tvAmountEquivalent = (TextView) findViewById(R.id.tvAmountEquivalent);
tvCardId.setText(mCard.getCIDDescription());
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
if (mCard.getBlockchain() == Blockchain.Token) {
Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard));
tvBalance.setText(html);
} else {
tvBalance.setText(engine.GetBalanceWithAlter(mCard));
}
tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard));
if(etAmount != null && mCard.getRemainingSignatures()<2)
{
etAmount.setEnabled(false);
}
if( !mCard.getAmountEquivalentDescriptionAvailable())
{
tvBalanceEquivalent.setError("Service unavailable");
}else{
tvBalanceEquivalent.setError(null);
}
etAmount.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
try {
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
tvAmountEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, etAmount.getText().toString()));
if (!mCard.getAmountEquivalentDescriptionAvailable()) {
tvAmountEquivalent.setError("Service unavailable");
}else{
tvAmountEquivalent.setError(null);
}
} catch (Exception e) {
e.printStackTrace();
tvAmountEquivalent.setText("");
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
if(mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet)
{
tvCurrency.setText(engine.GetBalanceCurrency(mCard));
use_mCurrency=false;
etAmount.setText(engine.GetBalanceValue(mCard));
}
else if(mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet)
{
Double balance = engine.GetBalanceLong(mCard) / (mCard.getBlockchain().getMultiplier() / 1000.0);
tvCurrency.setText("m" + mCard.getBlockchain().getCurrency());
use_mCurrency=true;
String output = FormatUtil.DoubleToString(balance);
etAmount.setText(output);
}
else if(mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet)
{
Double balance = engine.GetBalanceLong(mCard) / (mCard.getBlockchain().getMultiplier() / 1000.0);
tvCurrency.setText("m" + mCard.getBlockchain().getCurrency());
use_mCurrency=true;
String output = FormatUtil.DoubleToString(balance);
etAmount.setText(output);
}
else
{
tvCurrency.setText(engine.GetBalanceCurrency(mCard));
use_mCurrency=false;
etAmount.setText(engine.GetBalanceValue(mCard));
}
btnVerify.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String strAmount;
strAmount=etAmount.getText().toString();
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
try {
if(!engine.CheckAmount(mCard, etAmount.getText().toString()))
{
etAmount.setError("Not enough funds on your card");
}
}
catch(Exception e)
{
etAmount.setError("Unknown amount format");
return;
}
boolean checkAddress = engine.ValdateAddress(etWallet.getText().toString(), mCard);
if(!checkAddress)
{
etWallet.setError("Incorrect destination wallet address");
return;
}
if(etWallet.getText().toString().equals(mCard.getWallet()))
{
etWallet.setError("Destination wallet address equal source address");
return;
}
Intent intent = new Intent(getBaseContext(), ConfirmPaymentActivity.class);
intent.putExtra("UID", mCard.getUID());
intent.putExtra("Card", mCard.getAsBundle());
intent.putExtra("Wallet", etWallet.getText().toString());
intent.putExtra("Amount", strAmount);
startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT);
}
});
ivCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getBaseContext(), QRScanActivity.class);
startActivityForResult(intent, REQUEST_CODE_SCAN_QR);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.getExtras().containsKey("QRCode")) {
String code = data.getStringExtra("QRCode");
if(code.contains("bitcoin:"))
{
String tmp[] = code.split("bitcoin:");
code = tmp[1];
}
etWallet.setText(code);
}else if (requestCode == REQUEST_CODE_SEND_PAYMENT ) {
setResult(resultCode,data);
finish();
}
}
@Override
public void onTagDiscovered(Tag tag) {
try {
Log.w(getClass().getName(),"Ignore discovered tag!");
mNfcManager.IgnoreTag(tag);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
super.onPause();
mNfcManager.onPause();
}
@Override
public void onStop() {
super.onStop();
mNfcManager.onStop();
}
}

View file

@ -0,0 +1,333 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.NfcManager;
import com.tangem.cardReader.Util;
public class PurgeActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications {
public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER;
private Tangem_Card mCard;
private TextView tvCardID;
private NfcManager mNfcManager;
private static final String logTag = "Purge";
private ProgressBar progressBar;
private PurgeTask purgeTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_purge);
MainActivity.commonInit(getApplicationContext());
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card"));
tvCardID = (TextView) findViewById(R.id.tvCardID);
tvCardID.setText(mCard.getCIDDescription());
mNfcManager = new NfcManager(this, this);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onTagDiscovered(Tag tag) {
try {
// get IsoDep handle and run cardReader thread
final IsoDep isoDep = IsoDep.get(tag);
if (isoDep == null) {
throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err));
}
byte UID[] = tag.getId();
String sUID = Util.byteArrayToHexString(UID);
Log.v(logTag, "UID: " + sUID);
if (sUID.equals(mCard.getUID())) {
isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000);
purgeTask = new PurgeTask(isoDep, this);
purgeTask.start();
} else {
Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")");
mNfcManager.IgnoreTag(isoDep.getTag());
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
mNfcManager.onPause();
if (purgeTask != null) {
purgeTask.cancel(true);
}
super.onPause();
}
@Override
public void onStop() {
// dismiss enable NFC dialog
mNfcManager.onStop();
if (purgeTask != null) {
purgeTask.cancel(true);
}
super.onStop();
}
// @Override
// public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) {
// return mNfcManager.onCreateDialog(id, builder, li);
// }
private class PurgeTask extends Thread {
private String txOutAddress;
IsoDep mIsoDep;
CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public PurgeTask(IsoDep isoDep, CardProtocol.Notifications notifications) {
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(getBaseContext(), 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("PurgeTask", "[-- 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());
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i("PurgeTask", "[-- Finish purge --]");
mNotifications.OnReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (this.isAlive()) {
isCancelled = true;
join(500);
}
if (this.isAlive() && AllowInterrupt) {
interrupt();
mNotifications.OnReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void OnReadStart(CardProtocol cardProtocol) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(5);
}
});
}
public void OnReadFinish(final CardProtocol cardProtocol) {
purgeTask = null;
if (cardProtocol != null) {
if (cardProtocol.getError() == null) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN));
Intent intent = new Intent();
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
setResult(Activity.RESULT_OK, intent);
finish();
}
});
} else {
if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
Intent intent = new Intent();
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
intent.putExtra("message", "Cannot erase wallet. Make sure you enter correct PIN2!");
setResult(RESULT_INVALID_PIN, intent);
finish();
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
return;
} else {
progressBar.post(new Runnable() {
@Override
public void run() {
if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allreadyShowed) {
new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog");
}
} else {
Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show();
}
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
}
}
}
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
public void OnReadProgress(CardProtocol protocol, final int progress) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(progress);
}
});
}
public void OnReadCancel() {
purgeTask = null;
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
@Override
public void OnReadWait(final int msec) {
WaitSecurityDelayDialog.OnReadWait(this, msec);
}
@Override
public void OnReadBeforeRequest(int timeout) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout);
}
@Override
public void OnReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this);
}
}

View file

@ -0,0 +1,90 @@
package com.tangem.wallet;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import com.google.zxing.Result;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
public class QRScanActivity extends AppCompatActivity implements ZXingScannerView.ResultHandler{
private ZXingScannerView mScannerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_qrscan);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
Log.e("QRScanActivity","User hasn't granted permission to use camera");
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA}, 1);
}else {
runScanner();
}
}
void runScanner()
{
mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view
setContentView(mScannerView);
mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
mScannerView.startCamera();
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case 1: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i("QRScanActivity","permission was granted");
// permission was granted, yay! Do the
// contacts-related task you need to do.
runScanner();
} else {
Log.e("QRScanActivity","permission denied");
setResult(Activity.RESULT_CANCELED);
finish();
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
@Override
public void handleResult(Result result) {
Intent data=new Intent();
data.putExtra("QRCode", result.getText());
setResult(Activity.RESULT_OK, data);
finish();
}
@Override
protected void onPause() {
super.onPause();
if( mScannerView!=null ) mScannerView.stopCamera(); // Stop camera on pause
}
@Override
protected void onResume() {
super.onResume();
if( mScannerView!=null ) mScannerView.startCamera();
}
}

View file

@ -0,0 +1,228 @@
package com.tangem.wallet;
/**
* Created by Ilia on 07.01.2018.
*/
import java.util.Arrays;
import static com.tangem.wallet.ByteUtil.isNullOrZeroArray;
import static com.tangem.wallet.ByteUtil.isSingleZero;
public class RLP {
public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]);
/**
* Allow for content up to size of 2^64 bytes *
*/
private static final double MAX_ITEM_LENGTH = Math.pow(256, 8);
/**
* Reason for threshold according to Vitalik Buterin:
* - 56 bytes maximizes the benefit of both options
* - if we went with 60 then we would have only had 4 slots for long strings
* so RLP would not have been able to store objects above 4gb
* - if we went with 48 then RLP would be fine for 2^128 space, but that's way too much
* - so 56 and 2^64 space seems like the right place to put the cutoff
* - also, that's where Bitcoin's varint does the cutof
*/
private static final int SIZE_THRESHOLD = 56;
/** RLP encoding rules are defined as follows: */
/*
* For a single byte whose value is in the [0x00, 0x7f] range, that byte is
* its own RLP encoding.
*/
/**
* [0x80]
* If a string is 0-55 bytes long, the RLP encoding consists of a single
* byte with value 0x80 plus the length of the string followed by the
* string. The range of the first byte is thus [0x80, 0xb7].
*/
private static final int OFFSET_SHORT_ITEM = 0x80;
/**
* [0xb7]
* If a string is more than 55 bytes long, the RLP encoding consists of a
* single byte with value 0xb7 plus the length of the length of the string
* in binary form, followed by the length of the string, followed by the
* string. For example, a length-1024 string would be encoded as
* \xb9\x04\x00 followed by the string. The range of the first byte is thus
* [0xb8, 0xbf].
*/
private static final int OFFSET_LONG_ITEM = 0xb7;
/**
* [0xc0]
* If the total payload of a list (i.e. the combined length of all its
* items) is 0-55 bytes long, the RLP encoding consists of a single byte
* with value 0xc0 plus the length of the list followed by the concatenation
* of the RLP encodings of the items. The range of the first byte is thus
* [0xc0, 0xf7].
*/
private static final int OFFSET_SHORT_LIST = 0xc0;
public static byte[] encodeByte(byte singleByte) {
if ((singleByte & 0xFF) == 0) {
return new byte[]{(byte) OFFSET_SHORT_ITEM};
} else if ((singleByte & 0xFF) <= 0x7F) {
return new byte[]{singleByte};
} else {
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte};
}
}
public static byte[] encodeShort(short singleShort) {
if ((singleShort & 0xFF) == singleShort)
return encodeByte((byte) singleShort);
else {
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 2),
(byte) (singleShort >> 8 & 0xFF),
(byte) (singleShort >> 0 & 0xFF)};
}
}
public static byte[] encodeInt(int singleInt) {
if ((singleInt & 0xFF) == singleInt)
return encodeByte((byte) singleInt);
else if ((singleInt & 0xFFFF) == singleInt)
return encodeShort((short) singleInt);
else if ((singleInt & 0xFFFFFF) == singleInt)
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 3),
(byte) (singleInt >>> 16),
(byte) (singleInt >>> 8),
(byte) singleInt};
else {
return new byte[]{(byte) (OFFSET_SHORT_ITEM + 4),
(byte) (singleInt >>> 24),
(byte) (singleInt >>> 16),
(byte) (singleInt >>> 8),
(byte) singleInt};
}
}
private static final int OFFSET_LONG_LIST = 0xf7;
public static byte[] encodeElement2(byte[] srcData) {
if (srcData == null)
return new byte[]{(byte) OFFSET_SHORT_ITEM};
else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) {
return srcData;
} else if (srcData.length < SIZE_THRESHOLD) {
// length = 8X
byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length);
byte[] data = Arrays.copyOf(srcData, srcData.length + 1);
System.arraycopy(data, 0, data, 1, srcData.length);
data[0] = length;
return data;
} else {
// length of length = BX
// prefix = [BX, [length]]
int tmpLength = srcData.length;
byte byteNum = 0;
while (tmpLength != 0) {
++byteNum;
tmpLength = tmpLength >> 8;
}
byte[] lenBytes = new byte[byteNum];
for (int i = 0; i < byteNum; ++i) {
lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF);
}
// first byte = F7 + bytes.length
byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum);
System.arraycopy(data, 0, data, 1 + byteNum, srcData.length);
data[0] = (byte) (OFFSET_LONG_ITEM + byteNum);
System.arraycopy(lenBytes, 0, data, 1, lenBytes.length);
return data;
}
}
public static byte[] encodeElement(byte[] srcData) {
if (isNullOrZeroArray(srcData))
return new byte[]{(byte) OFFSET_SHORT_ITEM};
else if (isSingleZero(srcData))
return srcData;
else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) {
return srcData;
} else if (srcData.length < SIZE_THRESHOLD) {
// length = 8X
byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length);
byte[] data = Arrays.copyOf(srcData, srcData.length + 1);
System.arraycopy(data, 0, data, 1, srcData.length);
data[0] = length;
return data;
} else {
// length of length = BX
// prefix = [BX, [length]]
int tmpLength = srcData.length;
byte byteNum = 0;
while (tmpLength != 0) {
++byteNum;
tmpLength = tmpLength >> 8;
}
byte[] lenBytes = new byte[byteNum];
for (int i = 0; i < byteNum; ++i) {
lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF);
}
// first byte = F7 + bytes.length
byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum);
System.arraycopy(data, 0, data, 1 + byteNum, srcData.length);
data[0] = (byte) (OFFSET_LONG_ITEM + byteNum);
System.arraycopy(lenBytes, 0, data, 1, lenBytes.length);
return data;
}
}
public static byte[] encodeList(byte[]... elements) {
if (elements == null) {
return new byte[]{(byte) OFFSET_SHORT_LIST};
}
int totalLength = 0;
for (byte[] element1 : elements) {
totalLength += element1.length;
}
byte[] data;
int copyPos;
if (totalLength < SIZE_THRESHOLD) {
data = new byte[1 + totalLength];
data[0] = (byte) (OFFSET_SHORT_LIST + totalLength);
copyPos = 1;
} else {
// length of length = BX
// prefix = [BX, [length]]
int tmpLength = totalLength;
byte byteNum = 0;
while (tmpLength != 0) {
++byteNum;
tmpLength = tmpLength >> 8;
}
tmpLength = totalLength;
byte[] lenBytes = new byte[byteNum];
for (int i = 0; i < byteNum; ++i) {
lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF);
}
// first byte = F7 + bytes.length
data = new byte[1 + lenBytes.length + totalLength];
data[0] = (byte) (OFFSET_LONG_LIST + byteNum);
System.arraycopy(lenBytes, 0, data, 1, lenBytes.length);
copyPos = lenBytes.length + 1;
}
for (byte[] element : elements) {
System.arraycopy(element, 0, data, copyPos, element.length);
copyPos += element.length;
}
return data;
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.wallet;
import java.io.Serializable;
/**
* Created by Ilia on 07.01.2018.
*/
public interface RLPElement extends Serializable {
byte[] getRLPData();
}

View file

@ -0,0 +1,37 @@
package com.tangem.wallet;
import java.util.ArrayList;
/**
* Created by Ilia on 07.01.2018.
*/
public class RLPList extends ArrayList<RLPElement> implements RLPElement {
byte[] rlpData;
public void setRLPData(byte[] rlpData) {
this.rlpData = rlpData;
}
public byte[] getRLPData() {
return rlpData;
}
public static void recursivePrint(RLPElement element) {
if (element == null)
throw new RuntimeException("RLPElement object can't be null");
if (element instanceof RLPList) {
RLPList rlpList = (RLPList) element;
System.out.print("[");
for (RLPElement singleElement : rlpList)
recursivePrint(singleElement);
System.out.print("]");
} else {
String hex = BTCUtils.toHex(element.getRLPData());
System.out.print(hex + ", ");
}
}
}

View file

@ -0,0 +1,531 @@
package com.tangem.wallet;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.tangem.cardReader.NfcManager;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
public class RequestPINActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener {
public enum Mode {RequestPIN, RequestPIN2, RequestNewPIN, RequestNewPIN2, ConfirmNewPIN, ConfirmNewPIN2}
Mode mode;
boolean allowFingerprint = false;
private NfcManager mNfcManager;
private TextView tvPIN;
private StartFingerprintReaderTask mStartFingerprintReaderTask;
public static final String KEY_ALIAS = "pinKey";
public static final String KEYSTORE = "AndroidKeyStore";
private FingerprintManager fingerprintManager;
private FingerprintHelper fingerprintHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_request_pin);
MainActivity.commonInit(getApplicationContext());
mNfcManager = new NfcManager(this, this);
tvPIN = findViewById(R.id.pin);
OnClickListener onButtonNClick = new OnClickListener() {
@Override
public void onClick(View view) {
tvPIN.setText(tvPIN.getText() + (String) ((Button) view).getText());
}
};
Button btn0 = findViewById(R.id.btn0);
btn0.setOnClickListener(onButtonNClick);
Button btn1 = findViewById(R.id.btn1);
btn1.setOnClickListener(onButtonNClick);
Button btn2 = findViewById(R.id.btn2);
btn2.setOnClickListener(onButtonNClick);
Button btn3 = findViewById(R.id.btn3);
btn3.setOnClickListener(onButtonNClick);
Button btn4 = findViewById(R.id.btn4);
btn4.setOnClickListener(onButtonNClick);
Button btn5 = findViewById(R.id.btn5);
btn5.setOnClickListener(onButtonNClick);
Button btn6 = findViewById(R.id.btn6);
btn6.setOnClickListener(onButtonNClick);
Button btn7 = findViewById(R.id.btn7);
btn7.setOnClickListener(onButtonNClick);
Button btn8 = findViewById(R.id.btn8);
btn8.setOnClickListener(onButtonNClick);
Button btn9 = findViewById(R.id.btn9);
btn9.setOnClickListener(onButtonNClick);
Button btnBS = findViewById(R.id.btnBackspace);
btnBS.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
String S = tvPIN.getText().toString();
if (S.length() > 0) {
tvPIN.setText(S.substring(0, S.length() - 1));
}
}
});
Button btnContinue = findViewById(R.id.btnContinue);
btnContinue.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
doContinue();
}
});
mode = Mode.valueOf(getIntent().getStringExtra("mode"));
TextView tvPrompt = findViewById(R.id.pin_prompt);
if (mode == Mode.RequestNewPIN) {
if (PINStorage.haveEncryptedPIN()) {
allowFingerprint = true;
tvPrompt.setText("Enter new PIN or use fingerprint scanner");
} else {
tvPrompt.setText("Enter new PIN");
}
} else if (mode == Mode.ConfirmNewPIN) {
tvPrompt.setText("Confirm new PIN");
} else if (mode == Mode.RequestPIN) {
if (PINStorage.haveEncryptedPIN()) {
allowFingerprint = true;
tvPrompt.setText("Enter PIN or use fingerprint scanner");
} else {
tvPrompt.setText("Enter PIN");
}
} else if (mode == Mode.RequestNewPIN2) {
if (PINStorage.haveEncryptedPIN2()) {
allowFingerprint = true;
tvPrompt.setText("Enter new PIN2 or use fingerprint scanner");
} else {
tvPrompt.setText("Enter new PIN2");
}
} else if (mode == Mode.ConfirmNewPIN2) {
tvPrompt.setText("Confirm new PIN2");
} else if (mode == Mode.RequestPIN2) {
String UID = getIntent().getStringExtra("UID");
Tangem_Card mCard = new Tangem_Card(UID);
mCard.LoadFromBundle(getIntent().getBundleExtra("Card"));
if (mCard.PIN2 == Tangem_Card.PIN2_Mode.DefaultPIN2 || mCard.PIN2 == Tangem_Card.PIN2_Mode.Unchecked) {
// if we know PIN2 or not try default previously - use it
PINStorage.setPIN2(PINStorage.getDefaultPIN2());
setResult(Activity.RESULT_OK);
finish();
return;
}
if (PINStorage.haveEncryptedPIN2()) {
allowFingerprint = true;
tvPrompt.setText("Enter PIN2 or use fingerprint scanner");
} else {
tvPrompt.setText("Enter PIN2");
}
}
ImageView imgFingerprint = findViewById(R.id.imgFingerprint);
if (!allowFingerprint) {
imgFingerprint.setVisibility(View.GONE);
} else {
imgFingerprint.setVisibility(View.VISIBLE);
}
}
@Override
protected void onPause() {
super.onPause();
if (fingerprintHelper != null)
fingerprintHelper.cancel();
if (mStartFingerprintReaderTask != null) {
mStartFingerprintReaderTask.cancel(true);
mStartFingerprintReaderTask = null;
}
mNfcManager.onPause();
}
@Override
protected void onStop() {
super.onStop();
if (fingerprintHelper != null)
fingerprintHelper.cancel();
if (mStartFingerprintReaderTask != null) {
mStartFingerprintReaderTask.cancel(true);
mStartFingerprintReaderTask = null;
}
mNfcManager.onStop();
}
@Override
protected void onResume() {
super.onResume();
mNfcManager.onResume();
if (allowFingerprint) {
startFingerprintReader();
}
}
private void doContinue() {
if (mStartFingerprintReaderTask != null) {
return;
}
// Reset errors.
tvPIN.setError(null);
// Store values at the time of the login attempt.
String pin = tvPIN.getText().toString();
boolean cancel = false;
View focusView = null;
if (mode == Mode.ConfirmNewPIN) {
if (!pin.equals(getIntent().getStringExtra("newPIN"))) {
tvPIN.setError(getString(R.string.error_pin_confirmation_failed));
focusView = tvPIN;
cancel = true;
}
} else if (mode == Mode.ConfirmNewPIN2) {
if (!pin.equals(getIntent().getStringExtra("newPIN2"))) {
tvPIN.setError(getString(R.string.error_pin_confirmation_failed));
focusView = tvPIN;
cancel = true;
}
} else {
if (TextUtils.isEmpty(pin)) {
tvPIN.setError(getString(R.string.error_empty_pin));
focusView = tvPIN;
cancel = true;
}
}
if (cancel) {
focusView.requestFocus();
} else {
if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) {
Intent resultData = new Intent();
resultData.putExtra("newPIN", pin);
if (mode == Mode.ConfirmNewPIN) {
resultData.putExtra("confirmPIN", pin);
}
setResult(Activity.RESULT_OK, resultData);
finish();
} else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) {
Intent resultData = new Intent();
resultData.putExtra("newPIN2", pin);
if (mode == Mode.ConfirmNewPIN2) {
resultData.putExtra("confirmPIN2", pin);
}
setResult(Activity.RESULT_OK, resultData);
finish();
} else if (mode == Mode.RequestPIN) {
PINStorage.setUserPIN(pin);
setResult(Activity.RESULT_OK);
finish();
} else if (mode == Mode.RequestPIN2) {
PINStorage.setPIN2(pin);
setResult(Activity.RESULT_OK);
finish();
}
}
}
@Override
public void authenticationFailed(String error) {
doLog(error);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) {
doLog("Authentication succeeded!");
Cipher cipher = result.getCryptoObject().getCipher();
if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) {
Intent resultData = new Intent();
String pin = PINStorage.loadEncryptedPIN(cipher);
resultData.putExtra("newPIN", pin);
resultData.putExtra("confirmPIN", pin);
setResult(Activity.RESULT_OK, resultData);
finish();
} else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) {
Intent resultData = new Intent();
String pin = PINStorage.loadEncryptedPIN2(cipher);
resultData.putExtra("newPIN2", pin);
resultData.putExtra("confirmPIN2", pin);
setResult(Activity.RESULT_OK, resultData);
finish();
} else if (mode == Mode.RequestPIN) {
PINStorage.loadEncryptedPIN(cipher);
setResult(Activity.RESULT_OK);
} else if (mode == Mode.RequestPIN2) {
PINStorage.loadEncryptedPIN2(cipher);
setResult(Activity.RESULT_OK);
}
finish();
}
private void startFingerprintReader() {
if (!testFingerPrintSettings())
return;
if (!allowFingerprint)
return;
fingerprintHelper = new FingerprintHelper(RequestPINActivity.this);
mStartFingerprintReaderTask = new StartFingerprintReaderTask(this, fingerprintManager, fingerprintHelper);
mStartFingerprintReaderTask.execute((Void) null);
}
@Override
public void onTagDiscovered(Tag tag) {
try {
Log.w(getClass().getName(), "Ignore discovered tag!");
mNfcManager.IgnoreTag(tag);
} catch (IOException e) {
e.printStackTrace();
}
}
public static class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
private KeyStore keyStore;
private Cipher cipher;
private FingerprintManager.CryptoObject cryptoObject;
FingerprintManager fingerprintManager;
FingerprintHelper fingerprintHelper;
RequestPINActivity activity;
StartFingerprintReaderTask(RequestPINActivity activity, FingerprintManager fingerprintManager, FingerprintHelper fingerprintHelper) {
this.fingerprintManager = fingerprintManager;
this.fingerprintHelper = fingerprintHelper;
this.activity = activity;
}
@Override
protected Boolean doInBackground(Void... params) {
if (!getKeyStore())
return false;
if (!createNewKey(false))
return false;
if (!getCipher())
return false;
if (!initCipher(Cipher.DECRYPT_MODE))
return false;
return initCryptObject();
}
@Override
protected void onPostExecute(final Boolean success) {
onCancelled();
if (!success) {
doLog("Authentication failed!");
} else {
fingerprintHelper.startAuth(fingerprintManager, cryptoObject);
doLog("Authenticate using fingerprint!");
}
}
@Override
protected void onCancelled() {
activity.mStartFingerprintReaderTask = null;
}
private boolean getKeyStore() {
doLog("Getting keystore...");
try {
keyStore = KeyStore.getInstance(KEYSTORE);
keyStore.load(null); // Create empty keystore
return true;
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
public boolean createNewKey(boolean forceCreate) {
doLog("Creating new key...");
try {
if (forceCreate)
keyStore.deleteEntry(KEY_ALIAS);
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE);
generator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.build()
);
generator.generateKey();
doLog("Key created.");
} else
doLog("Key exists.");
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean getCipher() {
doLog("Getting cipher...");
try {
cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
return true;
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
private boolean initCipher(int mode) {
doLog("Initializing cipher...");
try {
keyStore.load(null);
SecretKey keyspec = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(mode, keyspec);
} else {
byte[] iv = null;
if (activity.mode == Mode.RequestPIN || activity.mode == Mode.RequestNewPIN || activity.mode == Mode.ConfirmNewPIN) {
iv = PINStorage.loadEncryptedIV();
} else if (activity.mode == Mode.RequestPIN2 || activity.mode == Mode.RequestNewPIN2 || activity.mode == Mode.ConfirmNewPIN2) {
iv = PINStorage.loadEncryptedIV2();
}
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(mode, keyspec, ivspec);
}
return true;
} catch (KeyPermanentlyInvalidatedException e) {
e.printStackTrace();
createNewKey(true); // Retry after clearing entry
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
private boolean initCryptObject() {
doLog("Initializing crypt object...");
try {
cryptoObject = new FingerprintManager.CryptoObject(cipher);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
public static void doLog(String text) {
// Log.e("FP", text);
}
@SuppressLint("NewApi")
private boolean testFingerPrintSettings() {
doLog("Testing Fingerprint Settings");
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
assert keyguardManager != null;
if (!keyguardManager.isKeyguardSecure()) {
doLog("User hasn't enabled Lock Screen");
return false;
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
doLog("User hasn't granted permission to use Fingerprint");
return false;
}
if (!fingerprintManager.hasEnrolledFingerprints()) {
doLog("User hasn't registered any fingerprints");
return false;
}
doLog("Fingerprint authentication is set.\n");
return true;
}
}

View file

@ -0,0 +1,543 @@
package com.tangem.wallet;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
//import com.afollestad.materialdialogs.AlertDialogWrapper;
public class SavePINActivity extends AppCompatActivity implements FingerprintHelper.FingerprintHelperListener {
private TextView tvPIN;
private CheckBox chkUseFingerprint;
private ConfirmWithFingerprintTask mConfirmWithFingerprintTask;
private KeyStore keyStore;
private Cipher cipher;
private FingerprintManager fingerprintManager;
private FingerprintManager.CryptoObject cryptoObject;
private FingerprintHelper fingerprintHelper;
private boolean UsePIN2=false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_save_pin);
MainActivity.commonInit(getApplicationContext());
UsePIN2=getIntent().getBooleanExtra("PIN2", false);
tvPIN = findViewById(R.id.pin);
OnClickListener onButtonNClick = new OnClickListener() {
@Override
public void onClick(View view) {
tvPIN.setText(String.format("%s%s", tvPIN.getText(),((Button) view).getText()));
}
};
if( UsePIN2 ) {
((TextView) findViewById(R.id.pin_prompt)).setText(R.string.enter_pin2_and_use_fingerprint_to_save_it);
}else{
((TextView) findViewById(R.id.pin_prompt)).setText(R.string.enter_pin_and_use_fingerprint_to_save_it);
}
chkUseFingerprint = (CheckBox) findViewById(R.id.chkUseFingerprint);
if( UsePIN2 ) {
chkUseFingerprint.setChecked(true);
chkUseFingerprint.setEnabled(false);
}else{
chkUseFingerprint.setChecked(PINStorage.haveEncryptedPIN());
chkUseFingerprint.setEnabled(true);
}
Button btn0 = findViewById(R.id.btn0);
btn0.setOnClickListener(onButtonNClick);
Button btn1 = findViewById(R.id.btn1);
btn1.setOnClickListener(onButtonNClick);
Button btn2 = findViewById(R.id.btn2);
btn2.setOnClickListener(onButtonNClick);
Button btn3 = findViewById(R.id.btn3);
btn3.setOnClickListener(onButtonNClick);
Button btn4 = findViewById(R.id.btn4);
btn4.setOnClickListener(onButtonNClick);
Button btn5 = findViewById(R.id.btn5);
btn5.setOnClickListener(onButtonNClick);
Button btn6 = findViewById(R.id.btn6);
btn6.setOnClickListener(onButtonNClick);
Button btn7 = findViewById(R.id.btn7);
btn7.setOnClickListener(onButtonNClick);
Button btn8 = findViewById(R.id.btn8);
btn8.setOnClickListener(onButtonNClick);
Button btn9 = findViewById(R.id.btn9);
btn9.setOnClickListener(onButtonNClick);
Button btnBS = findViewById(R.id.btnBackspace);
btnBS.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
String S = tvPIN.getText().toString();
if (S.length() > 0) {
tvPIN.setText(S.substring(0, S.length() - 1));
}
}
});
Button btnSave = findViewById(R.id.btnSavePIN);
btnSave.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
doSavePIN();
}
});
Button btnDelete = findViewById(R.id.btnDeletePIN);
btnDelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
doDeletePIN();
}
});
}
@Override
protected void onPause() {
super.onPause();
if (fingerprintHelper != null)
fingerprintHelper.cancel();
if (mConfirmWithFingerprintTask != null)
mConfirmWithFingerprintTask.cancel(true);
}
@Override
protected void onStop() {
super.onStop();
if (fingerprintHelper != null)
fingerprintHelper.cancel();
if (mConfirmWithFingerprintTask != null)
mConfirmWithFingerprintTask.cancel(true);
}
private enum OnConfirmAction {Save, DeleteEncryptedAndSave, Delete}
OnConfirmAction onConfirmAction;
private void doSavePIN() {
if (mConfirmWithFingerprintTask != null) {
return;
}
tvPIN.setError(null);
String pin = tvPIN.getText().toString();
boolean cancel = false;
View focusView = null;
if (TextUtils.isEmpty(pin)) {
tvPIN.setError(getString(R.string.error_empty_pin));
focusView = tvPIN;
cancel = true;
}
if (cancel) {
focusView.requestFocus();
} else {
if( UsePIN2 )
{
if (!testFingerPrintSettings()) {
tvPIN.postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 2000);
return;
}
onConfirmAction = OnConfirmAction.Save;
mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask();
mConfirmWithFingerprintTask.execute((Void) null);
}else {
if (chkUseFingerprint.isChecked() || PINStorage.haveEncryptedPIN()) {
if (!testFingerPrintSettings()) {
tvPIN.postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 2000);
return;
}
if (chkUseFingerprint.isChecked()) {
onConfirmAction = OnConfirmAction.Save;
} else {
onConfirmAction = OnConfirmAction.DeleteEncryptedAndSave;
}
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
//showProgress(true);
mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask();
mConfirmWithFingerprintTask.execute((Void) null);
} else {
PINStorage.savePIN(tvPIN.getText().toString());
finish();
}
}
}
}
private void doDeletePIN() {
if( UsePIN2 )
{
if (PINStorage.haveEncryptedPIN2()) {
if (!testFingerPrintSettings()) {
tvPIN.postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 2000);
return;
}
onConfirmAction = OnConfirmAction.Delete;
mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask();
mConfirmWithFingerprintTask.execute((Void) null);
} else {
tvPIN.setText("");
finish();
}
}else {
if (chkUseFingerprint.isChecked() || PINStorage.haveEncryptedPIN()) {
if (!testFingerPrintSettings()) {
tvPIN.postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 2000);
return;
}
onConfirmAction = OnConfirmAction.Delete;
mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask();
mConfirmWithFingerprintTask.execute((Void) null);
} else {
tvPIN.setText("");
PINStorage.deletePIN();
finish();
}
}
}
@Override
public void authenticationFailed(String error) {
print(error);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) {
print("Authentication succeeded!");
cipher = result.getCryptoObject().getCipher();
switch (onConfirmAction) {
case Save:
String textToEncrypt = tvPIN.getText().toString();
if( UsePIN2 )
{
PINStorage.saveEncryptedPIN2(cipher, textToEncrypt);
}else {
PINStorage.saveEncryptedPIN(cipher, textToEncrypt);
}
print(R.string.pin_save_success);
break;
case Delete:
if( UsePIN2 )
{
PINStorage.deleteEncryptedPIN2();
}else {
PINStorage.deleteEncryptedPIN();
PINStorage.deletePIN();
}
tvPIN.setText("");
break;
case DeleteEncryptedAndSave:
if( UsePIN2 )
{
PINStorage.deleteEncryptedPIN2();
PINStorage.saveEncryptedPIN2(cipher, tvPIN.getText().toString());
}else {
PINStorage.deleteEncryptedPIN();
PINStorage.savePIN(tvPIN.getText().toString());
}
break;
}
dFingerPrintConfirmation.dismiss();
finish();
}
private class ConfirmWithFingerprintTask extends AsyncTask<Void, Void, Boolean> {
ConfirmWithFingerprintTask() {
fingerprintHelper = new FingerprintHelper(SavePINActivity.this);
}
@Override
protected Boolean doInBackground(Void... params) {
if (!getKeyStore())
return false;
if (!createNewKey(false))
return false;
if (!getCipher())
return false;
return initCipher(Cipher.ENCRYPT_MODE) && initCryptObject();
}
@Override
protected void onPostExecute(final Boolean success) {
onCancelled();
if (!success) {
Toast.makeText(getBaseContext(), R.string.pin_save_fail, Toast.LENGTH_LONG).show();
} else {
print("Confirm PIN action using fingerprint!");
fingerprintHelper.startAuth(fingerprintManager, cryptoObject);
CreateFingerPrintConfirmationDialog();
}
}
@Override
protected void onCancelled() {
mConfirmWithFingerprintTask = null;
if (dFingerPrintConfirmation != null) {
dFingerPrintConfirmation.cancel();
}
}
}
public void print(String text) {
// Log.e("FP", text);
}
public void print(int id) {
print(getString(id));
}
@SuppressLint("NewApi")
private boolean testFingerPrintSettings() {
print("Testing Fingerprint Settings");
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {
print("User hasn't enabled Lock Screen");
Toast.makeText(getBaseContext(), "User hasn't enabled Lock Screen", Toast.LENGTH_LONG).show();
return false;
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
print("User hasn't granted permission to use Fingerprint");
Toast.makeText(getBaseContext(), "User hasn't granted permission to use Fingerprint", Toast.LENGTH_LONG).show();
return false;
}
if (!fingerprintManager.hasEnrolledFingerprints()) {
print("User hasn't registered any fingerprints");
Toast.makeText(getBaseContext(), "User hasn't registered any fingerprints", Toast.LENGTH_LONG).show();
return false;
}
print("Fingerprint authentication is set.\n");
return true;
}
private boolean getKeyStore() {
print("Getting keystore...");
try {
keyStore = KeyStore.getInstance(RequestPINActivity.KEYSTORE);
keyStore.load(null); // Create empty keystore
return true;
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
public boolean createNewKey(boolean forceCreate) {
print("Creating new key...");
try {
if (forceCreate)
keyStore.deleteEntry(RequestPINActivity.KEY_ALIAS);
if (!keyStore.containsAlias(RequestPINActivity.KEY_ALIAS)) {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, RequestPINActivity.KEYSTORE);
generator.init(new KeyGenParameterSpec.Builder(RequestPINActivity.KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.build()
);
generator.generateKey();
print("Key created.");
} else
print("Key exists.");
return true;
} catch (Exception e) {
print(e.getMessage());
}
return false;
}
private boolean getCipher() {
print("Getting cipher...");
try {
cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
return true;
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
private boolean initCipher(int mode) {
print("Initializing cipher...");
try {
keyStore.load(null);
SecretKey keyspec = (SecretKey) keyStore.getKey(RequestPINActivity.KEY_ALIAS, null);
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(mode, keyspec);
} else {
byte[] iv = PINStorage.loadEncryptedIV();
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(mode, keyspec, ivspec);
}
return true;
} catch (KeyPermanentlyInvalidatedException e) {
e.printStackTrace();
createNewKey(true); // Retry after clearing entry
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
private boolean initCryptObject() {
print("Initializing crypt object...");
try {
cryptoObject = new FingerprintManager.CryptoObject(cipher);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
Dialog dFingerPrintConfirmation = null;
private void CreateFingerPrintConfirmationDialog() {
// final AlertDialogWrapper.Builder b = new AlertDialogWrapper.Builder(this);
// switch (onConfirmAction)
// {
// case Save:
// if( UsePIN2) {
// b.setTitle("Confirm new PIN2 saving ");
// }else{
// b.setTitle("Confirm new PIN saving ");
// }
// break;
// case Delete:
// if( UsePIN2 ) {
// b.setTitle("Confirm PIN2 deleting");
// }else{
// b.setTitle("Confirm PIN deleting");
// }
// break;
// case DeleteEncryptedAndSave:
// if( UsePIN2 ) {
// b.setTitle("Confirm deleting old saved PIN2");
// }else {
// b.setTitle("Confirm deleting old saved PIN");
// }
// break;
// }
// View view = getLayoutInflater().inflate(R.layout.dialog_fingerprint_confirmation, null);
// b.setView(view);
// dFingerPrintConfirmation = b.show();
// dFingerPrintConfirmation.setOnCancelListener(new DialogInterface.OnCancelListener() {
// @Override
// public void onCancel(DialogInterface dialog) {
// fingerprintHelper.cancel();
// print("Cancel fingerprint confirmation");
// }
// });
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.wallet;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class SelectBlockchainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_blockchain);
// Spinner spBlockchain = (Spinner) findViewById(R.id.spBlockchain);
// ArrayAdapter<Blockchain> adapter = new ArrayAdapter<Blockchain>(this, android.R.layout.simple_spinner_item, Blockchain.values());
// spBlockchain.setAdapter(adapter);
// spBlockchain.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
// @Override
// public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// Intent intent = new Intent();
// intent.putExtra("blockchain", Blockchain.values()[position].toString());
// setResult(RESULT_OK, intent);
// finish();
// }
//
// @Override
// public void onNothingSelected(AdapterView<?> parent) {
//
// }
// });
}
}

View file

@ -0,0 +1,208 @@
package com.tangem.wallet;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import java.math.BigInteger;
import java.util.List;
public class SendTransactionActivity extends AppCompatActivity {
ProgressBar progressBar;
private Tangem_Card mCard;
private String tx;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_send_transaction);
MainActivity.commonInit(getApplicationContext());
progressBar = findViewById(R.id.progressBar);
Intent intent = getIntent();
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(intent.getExtras().getBundle("Card"));
tx = intent.getStringExtra("TX");
CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain());
if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) {
ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain());
Infura_Request req = Infura_Request.SendTransaction(mCard.getWallet(), tx);
req.setID(67);
req.setBlockchain(mCard.getBlockchain());
task.execute(req);
} else if (mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet ) {
String nodeAddress = engine.GetNode(mCard);
int nodePort = engine.GetNodePort(mCard);
ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort);
connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx));
}
else if (mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet ) {
String nodeAddress = engine.GetNode(mCard);
int nodePort = engine.GetNodePort(mCard);
ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort);
connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx));
}
}
@Override
public boolean onKeyDown(int keycode, KeyEvent e) {
switch (keycode) {
case KeyEvent.KEYCODE_BACK:
Toast.makeText(getBaseContext(),"Please wait while the payment is sent...",Toast.LENGTH_LONG).show();
return true;
}
return super.onKeyDown(keycode, e);
}
void FinishWithError(String Message) {
Intent intent = new Intent();
intent.putExtra("message", "Failed to send transaction. Try again.");
setResult(MainActivity.RESULT_CANCELED, intent);
finish();
}
void FinishWithSuccess() {
Intent intent = new Intent();
intent.putExtra("message", "Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while");
setResult(MainActivity.RESULT_OK, intent);
finish();
}
private class ETHRequestTask extends Infura_Task {
ETHRequestTask(Blockchain blockchain){
super(blockchain);
}
@Override
protected void onPostExecute(List<Infura_Request> requests) {
super.onPostExecute(requests);
for (Infura_Request request : requests) {
try {
if (request.error == null) {
if (request.isMethod(Infura_Request.METHOD_ETH_SendRawTransaction)) {
try {
String hashTX = "";
try {
String tmp = request.getResultString();
hashTX = tmp;
}catch(JSONException e)
{
JSONObject msg = request.getAnswer();
JSONObject err = msg.getJSONObject("error");
hashTX = err.getString("message");
LastSignStorage.setLastMessage(mCard.getWallet(), hashTX);
FinishWithError(hashTX);
return;
}
try {
if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) {
hashTX = hashTX.substring(2);
}
BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ
LastSignStorage.setTxWasSend(mCard.getWallet());
LastSignStorage.setLastMessage(mCard.getWallet(), "");
BigInteger nonce = mCard.GetConfirmTXCount();
nonce.add(BigInteger.valueOf(1));
mCard.SetConfirmTXCount(nonce);
Log.e("TX_RESULT", hashTX);
FinishWithSuccess();
}catch(Exception e)
{
FinishWithError(hashTX);
}
} catch (JSONException e) {
e.printStackTrace();
FinishWithError(e.toString());
}
}
} else if (request.error != null) {
FinishWithError(request.error);
}
} catch (JSONException e) {
e.printStackTrace();
FinishWithError(e.toString());
}
}
}
}
private class ConnectTask extends Electrum_Task {
public ConnectTask(String host, int port) {
super(host, port);
}
public ConnectTask(String host, int port, SharedData sharedData) {
super(host, port, sharedData);
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(List<Electrum_Request> requests) {
super.onPostExecute(requests);
CoinEngine engine = CoinEngineFactory.Create(Blockchain.Bitcoin);
for (Electrum_Request request : requests) {
try {
if (request.error == null) {
if (request.isMethod(Electrum_Request.METHOD_SendTransaction)) {
try {
String hashTX = request.getResultString();
try
{
LastSignStorage.setLastMessage(mCard.getWallet(), hashTX);
if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) {
hashTX = hashTX.substring(2);
}
BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ
LastSignStorage.setTxWasSend(mCard.getWallet());
LastSignStorage.setLastMessage(mCard.getWallet(), "");
Log.e("TX_RESULT", hashTX);
FinishWithSuccess();
}catch(Exception e)
{
engine.SwitchNode(null);
FinishWithError(hashTX);
return;
}
} catch (JSONException e) {
e.printStackTrace();
engine.SwitchNode(null);
FinishWithError(e.toString());
}
}
} else if (request.error != null) {
engine.SwitchNode(null);
FinishWithError(request.error);
}
} catch (JSONException e) {
e.printStackTrace();
engine.SwitchNode(null);
FinishWithError(e.toString());
}
}
}
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.wallet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by Ilia on 12.04.2018.
*/
public class SharedData
{
public static int COUNT_REQUEST = 3;
public AtomicInteger requestCounter;
public int allRequest;
public AtomicInteger errorRequest;
public SharedData(int requstCount)
{
allRequest = requstCount;
errorRequest = new AtomicInteger(0);
requestCounter = new AtomicInteger(0);
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,326 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.NfcManager;
import com.tangem.cardReader.Util;
public class SwapPINActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications {
public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER;
private Tangem_Card mCard;
private NfcManager mNfcManager;
private static final String logTag = "SwapPIN";
private ProgressBar progressBar;
private SwapPINTask swapPinTask;
private String newPIN, newPIN2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swap_pin);
MainActivity.commonInit(getApplicationContext());
mCard = new Tangem_Card(getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card"));
newPIN = getIntent().getStringExtra("newPIN");
newPIN2 = getIntent().getStringExtra("newPIN2");
TextView tvCardID = findViewById(R.id.tvCardID);
tvCardID.setText(mCard.getCIDDescription());
mNfcManager = new NfcManager(this, this);
progressBar = findViewById(R.id.progressBar);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onTagDiscovered(Tag tag) {
try {
// get IsoDep handle and run cardReader thread
final IsoDep isoDep = IsoDep.get(tag);
if (isoDep == null) {
throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err));
}
byte UID[] = tag.getId();
String sUID = Util.byteArrayToHexString(UID);
Log.v(logTag, "UID: " + sUID);
if (sUID.equals(mCard.getUID())) {
isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000);
swapPinTask = new SwapPINTask(isoDep, this);
swapPinTask.start();
} else {
Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")");
mNfcManager.IgnoreTag(isoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
mNfcManager.onPause();
if (swapPinTask != null) {
swapPinTask.cancel(true);
}
super.onPause();
}
@Override
public void onStop() {
// dismiss enable NFC dialog
mNfcManager.onStop();
if (swapPinTask != null) {
swapPinTask.cancel(true);
}
super.onStop();
}
private class SwapPINTask extends Thread {
IsoDep mIsoDep;
CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
SwapPINTask(IsoDep isoDep, CardProtocol.Notifications notifications) {
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(getBaseContext(), 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("SwapTask", "[-- Start swap pin --]");
if (isCancelled) return;
if (mCard.getPauseBeforePIN2() > 0) {
mNotifications.OnReadWait(mCard.getPauseBeforePIN2());
}
// try {
protocol.run_SwapPIN(PINStorage.getPIN2(), newPIN, newPIN2, false);
protocol.setPIN(newPIN);
mCard.setPIN(newPIN);
// } finally {
// mNotifications.OnReadWait(0);
// }
mNotifications.OnReadProgress(protocol, 50);
protocol.run_Read();
mNotifications.OnReadProgress(protocol, 100);
} finally {
mNfcManager.IgnoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i("SwapPINTask", "[-- Finish purge --]");
mNotifications.OnReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (this.isAlive()) {
isCancelled = true;
join(500);
}
if (this.isAlive() && AllowInterrupt) {
interrupt();
mNotifications.OnReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void OnReadStart(CardProtocol cardProtocol) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(5);
}
});
}
public void OnReadFinish(final CardProtocol cardProtocol) {
swapPinTask = null;
if (cardProtocol != null) {
if (cardProtocol.getError() == null) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN));
Intent intent = new Intent();
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
setResult(Activity.RESULT_OK, intent);
finish();
}
});
} else if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
Intent intent = new Intent();
intent.putExtra("message", "Cannot change PIN(s). Make sure you enter correct PIN2!");
intent.putExtra("UID", cardProtocol.getCard().getUID());
intent.putExtra("Card", cardProtocol.getCard().getAsBundle());
setResult(RESULT_INVALID_PIN, intent);
finish();
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
return;
} else {
progressBar.post(new Runnable() {
@Override
public void run() {
if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allreadyShowed) {
new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog");
}
} else {
Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show();
}
progressBar.setProgress(100);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED));
}
});
}
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
}
public void OnReadProgress(CardProtocol protocol, final int progress) {
progressBar.post(new Runnable() {
@Override
public void run() {
progressBar.setProgress(progress);
}
});
}
public void OnReadCancel() {
swapPinTask = null;
progressBar.postDelayed(new Runnable() {
@Override
public void run() {
try {
progressBar.setProgress(0);
progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY));
progressBar.setVisibility(View.INVISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 500);
}
@Override
public void OnReadWait(final int msec) {
WaitSecurityDelayDialog.OnReadWait(this, msec);
}
@Override
public void OnReadBeforeRequest(int timeout) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout);
}
@Override
public void OnReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
package com.tangem.wallet;
import android.net.Uri;
import android.util.Log;
import com.google.common.base.Strings;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.TLV;
import org.bitcoinj.core.ECKey;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Date;
import static com.tangem.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 20.03.2018.
*/
public class TokenEngine extends CoinEngine{
public String GetNextNode(Tangem_Card mCard)
{
return "abc1.hsmiths.com";
}
public int GetNextNodePort(Tangem_Card mCard)
{
return 60001;
}
public String GetNode(Tangem_Card mCard)
{
return "abc1.hsmiths.com";
}
public int GetNodePort(Tangem_Card mCard)
{
return 60001;
}
public void SwitchNode(Tangem_Card mCard)
{
}
public boolean AwaitingConfirmation(Tangem_Card card)
{
return false;
}
public boolean InOutPutVisible()
{
return false;
}
public String GetBalanceCurrency(Tangem_Card card)
{
String currency = card.getTokenSymbol();
if(Strings.isNullOrEmpty(currency))
return "NoN";
return currency;
}
public String GetFeeCurrency()
{
return "Gwei";
}
BigDecimal convertToEth(String value)
{
BigInteger m = new BigInteger(value, 10);
BigDecimal n = new BigDecimal(m);
BigDecimal d = n.divide(new BigDecimal("1000000000000000000"));
d = d.setScale(8, RoundingMode.DOWN);
return d;
}
public int GetTokenDecimals(Tangem_Card card)
{
return card.getTokensDecimal();
}
public String GetContractAddress(Tangem_Card card)
{
return card.getContractAddress();
}
public boolean IsNeedCheckNode()
{
return false;
}
public boolean ValdateAddress(String address, Tangem_Card card) {
if (address == null || address.isEmpty()) {
return false;
}
if(!address.startsWith("0x")&&!address.startsWith("0X"))
{
return false;
}
if(address.length()!=42)
{
return false;
}
return true;
}
public String GetBalanceAlterValue(Tangem_Card mCard)
{
String dec = mCard.getDecimalBalanceAlter();
BigDecimal d = convertToEth(dec);
String s = d.toString();
String pattern = "#0.000"; // If you like 4 zeros
DecimalFormat myFormatter = new DecimalFormat(pattern);
String output = myFormatter.format(d);
return output;
}
public String GetBalanceValue(Tangem_Card mCard)
{
if(!HasBalanceInfo(mCard))
return "-- -- -- " + GetBalanceCurrency(mCard);
String dec = mCard.getDecimalBalance();
BigDecimal d = new BigDecimal(dec);
BigDecimal p = new BigDecimal(10);
p = p.pow(GetTokenDecimals(mCard));
BigDecimal l = d.divide(p);
String pattern = "#0.000"; // If you like 4 zeros
DecimalFormat myFormatter = new DecimalFormat(pattern);
String output = myFormatter.format(l);
return output;
}
public boolean CheckAmount(Tangem_Card card, String amount) throws Exception
{
DecimalFormat decimalFormat = GetDecimalFormat();
BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); //new BigDecimal(strAmount);
BigDecimal maxValue = new BigDecimal(GetBalanceValue(card));
if(amountValue.compareTo(maxValue) > 0 )
{
return false;
}
return true;
}
public Long GetBalanceLong(Tangem_Card mCard)
{
return mCard.getBalance();
}
public boolean IsBalanceAlterNotZero(Tangem_Card card)
{
String balance = card.getDecimalBalanceAlter();
if(balance == null || balance == "")
return false;
BigDecimal bi = new BigDecimal(balance);
if (BigDecimal.ZERO.compareTo(bi) == 0)
return false;
return true;
}
public boolean IsBalanceNotZero(Tangem_Card card)
{
String balance = card.getDecimalBalance();
if(balance == null || balance == "")
return false;
BigDecimal bi = new BigDecimal(balance);
if (BigDecimal.ZERO.compareTo(bi) == 0)
return false;
return true;
}
public boolean HasBalanceInfo(Tangem_Card card)
{
String balance = card.getDecimalBalance();
if(balance == null || balance == "")
return false;
String balanceEx = card.getDecimalBalanceAlter();
if(balanceEx == null || balanceEx == "")
return false;
return true;
}
@Override
public String GetBalanceEquivalent(Tangem_Card mCard) {
if(!HasBalanceInfo(mCard)){
return "-- -- -- ";
}
String dec = mCard.getDecimalBalance();
BigDecimal d = convertToEth(dec);
return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
@Override
public String GetBalance(Tangem_Card mCard) {
if(!HasBalanceInfo(mCard)){
return "-- -- -- " + GetBalanceCurrency(mCard);
}
String output = GetBalanceValue(mCard);
String s = output + " " + GetBalanceCurrency(mCard);
return s;
}
public String GetBalanceWithAlter(Tangem_Card mCard)
{
//return GetBalance(mCard) + "\n(" + GetBalanceAlterValue(mCard) + " ETH)";
return " " + GetBalance(mCard) + " <br><small><small> + " + GetBalanceAlterValue(mCard) + " ETH for gas</small></small>";
}
public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
Keccak256 kec = new Keccak256();
int lenPk = pkUncompressed.length;
if (lenPk < 2) {
throw new IllegalArgumentException("Uncompress public key length is invald");
}
byte[] cleanKey = new byte[lenPk - 1];
for (int i = 0; i < cleanKey.length; ++i) {
cleanKey[i] = pkUncompressed[i + 1];
}
byte[] r = kec.digest(cleanKey);
byte[] address = new byte[20];
for (int i = 0; i < 20; ++i) {
address[i] = r[i + 12];
}
return String.format("0x%s", BTCUtils.toHex(address));
}
@Override
public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception {
throw new Exception("Not implemented");
}
@Override
public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
@Override
public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value)
{
BigDecimal d = new BigDecimal(value);
return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
public String GetFeeEqualentDescriptor(Tangem_Card mCard, String value)
{
BigDecimal d = new BigDecimal(value);
return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRateAlter());
}
public Uri getShareWalletURIExplorer(Tangem_Card mCard)
{
return Uri.parse("https://etherscan.io/token/"+GetContractAddress(mCard)+"?a=" + mCard.getWallet());
}
public Uri getShareWalletURI(Tangem_Card mCard)
{
return Uri.parse("" + mCard.getWallet());
}
public boolean CheckUnspentTransaction(Tangem_Card mCard)
{
return true;
}
public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits)
{
Long fee = null;
BigDecimal amount = null;
try {
amount = new BigDecimal(GetBalanceAlterValue(mCard));//mCard.InternalUnitsFromString(amountValue);
fee = mCard.InternalUnitsFromString(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if(fee == null || amount == null)
return false;
if(fee == 0 || amount.compareTo(BigDecimal.ZERO) == 0)
return false;
if(fee < minFeeInInternalUnits)
return false;
BigDecimal tmpFee = new BigDecimal(feeValue);
BigDecimal tmpAmount = amount;
tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000"));
if (tmpFee.compareTo(tmpAmount) > 0)
return false;
return true;
}
public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee)
{
BigDecimal gweFee = new BigDecimal(fee);
gweFee = gweFee.divide(new BigDecimal("1000000000"));
gweFee = gweFee.setScale(18, RoundingMode.DOWN);
return GetFeeEqualentDescriptor(mCard, gweFee.toString());
}
public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception {
BigInteger nonceValue = mCard.GetConfirmTXCount();
byte[] pbKey = mCard.getWalletPublicKey();
boolean flag = (mCard.getSigningMethod()== Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = mCard.getIssuer();
BigInteger fee = new BigInteger(feeValue, 10);
BigDecimal amountDecValue = new BigDecimal(amountValue);
int d = GetTokenDecimals(mCard);
BigDecimal amountDec = new BigDecimal("10");
amountDec = amountDec.pow(d);
amountDec = amountDecValue.multiply(amountDec);
//amountDec = amountDec.multiply(new BigDecimal("1000000000"));
BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10);
//amount = amount.subtract(fee);
BigInteger nonce = nonceValue;
BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000));
BigInteger gasLimit = BigInteger.valueOf(60000);
Integer chainId = ETH_Transaction.ChainEnum.Mainnet.getValue();
BigInteger amountZero = BigInteger.ZERO;
Long multiplicator = 1000000000L;
gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator));
String to = toValue;
if (to.startsWith("0x") || to.startsWith("0X")) {
to = to.substring(2);
}
String contractAddress = GetContractAddress(mCard);
if (contractAddress.startsWith("0x") || contractAddress.startsWith("0X")) {
contractAddress = contractAddress.substring(2);
}
String amountLeadZero = amount.toString(16);
if (amountLeadZero.startsWith("0x") || amountLeadZero.startsWith("0X")) {
amountLeadZero = amountLeadZero.substring(2);
}
while(amountLeadZero.length() < 64)
{
amountLeadZero = "0" + amountLeadZero;
}
String cmd = "a9059cbb000000000000000000000000"+to+amountLeadZero; //TODO only for BAT
byte[] data = BTCUtils.fromHex(cmd);
ETH_Transaction tx = ETH_Transaction.create(contractAddress, amountZero, nonce, gasPrice, gasLimit, chainId, data);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if(!f)
{
Log.e("ETH-CHECK", "Sign Failed.");
}
tx.signature = new ECDSASignature_ETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
byte[] realTX = tx.getEncoded();
return realTX;
}
}

View file

@ -0,0 +1,632 @@
package com.tangem.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
import org.spongycastle.jcajce.provider.symmetric.ARC4;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Stack;
@SuppressWarnings("WeakerAccess")
public final class Transaction {
public final int version;
public final Input[] inputs;
public final Output[] outputs;
public final int lockTime;
public Transaction(byte[] rawBytes) throws BitcoinException {
if (rawBytes == null) {
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "empty input");
}
BitcoinInputStream bais = null;
try {
bais = new BitcoinInputStream(rawBytes);
version = bais.readInt32();
if (version != 1 && version != 2 && version != 3) {
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unsupported TX version", version);
}
int inputsCount = 0;
int first = bais.readByte();
if(first == 0)
{
int skip = bais.readByte();
inputsCount = bais.readByte();
}
else
{
inputsCount = first;
}
//int inputsCount = (int) bais.readVarInt(); TODO:
inputs = new Input[inputsCount];
for (int i = 0; i < inputsCount; i++) {
OutPoint outPoint = new OutPoint(BTCUtils.reverse(bais.readChars(32)), bais.readInt32());
byte[] script = bais.readChars((int) bais.readVarInt());
int sequence = bais.readInt32();
inputs[i] = new Input(outPoint, new Script(script), sequence);
}
int outputsCount = (int) bais.readVarInt();
outputs = new Output[outputsCount];
for (int i = 0; i < outputsCount; i++) {
long value = bais.readInt64();
long scriptSize = bais.readVarInt();
if (scriptSize < 0 || scriptSize > 10_000_000) {
throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Script size for output " + i +
" is strange (" + scriptSize + " bytes).");
}
byte[] script = bais.readChars((int) scriptSize);
outputs[i] = new Output(value, new Script(script));
}
lockTime = bais.readInt32();
} catch (EOFException e) {
throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "TX incomplete");
} catch (IOException e) {
throw new IllegalArgumentException("Unable to read TX");
} catch (Error e) {
throw new IllegalArgumentException("Unable to read TX: " + e);
} finally {
if (bais != null) {
try {
bais.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public Transaction(Input[] inputs, Output[] outputs, int lockTime) {
this.version = 1;
this.inputs = inputs;
this.outputs = outputs;
this.lockTime = lockTime;
}
public byte[] getBytes() {
BitcoinOutputStream baos = new BitcoinOutputStream();
try {
baos.writeInt32(version);
baos.writeVarInt(inputs.length);
for (Input input : inputs) {
baos.write(BTCUtils.reverse(input.outPoint.hash));
baos.writeInt32(input.outPoint.index);
int scriptLen = input.script == null ? 0 : input.script.bytes.length;
baos.writeVarInt(scriptLen);
if (scriptLen > 0) {
baos.write(input.script.bytes);
}
baos.writeInt32(input.sequence);
}
baos.writeVarInt(outputs.length);
for (Output output : outputs) {
baos.writeInt64(output.value);
int scriptLen = output.script == null ? 0 : output.script.bytes.length;
baos.writeVarInt(scriptLen);
if (scriptLen > 0) {
baos.write(output.script.bytes);
}
}
baos.writeInt32(lockTime);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return baos.toByteArray();
}
@Override
public String toString() {
return "{" +
"\n\"inputs\":\n" + printAsJsonArray(inputs) +
",\n\"outputs\":\n" + printAsJsonArray(outputs) +
",\n\"lockTime\":\"" + lockTime + "\"}\n";
}
private String printAsJsonArray(Object[] a) {
if (a == null) {
return "null";
}
if (a.length == 0) {
return "[]";
}
int iMax = a.length - 1;
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; ; i++) {
sb.append(String.valueOf(a[i]));
if (i == iMax)
return sb.append(']').toString();
sb.append(",\n");
}
}
public static class Input {
public final OutPoint outPoint;
public final Script script;
public final int sequence;
public Input(OutPoint outPoint, Script script, int sequence) {
this.outPoint = outPoint;
this.script = script;
this.sequence = sequence;
}
@Override
public String toString() {
return "{\n\"outPoint\":" + outPoint + ",\n\"script\":\"" + script + "\",\n\"sequence\":\"" + Integer.toHexString(sequence) + "\"\n}\n";
}
}
public static class OutPoint {
public final byte[] hash;//32-byte hash of the transaction from which we want to redeem an output
public final int index;//Four-byte field denoting the output index we want to redeem from the transaction with the above hash (output number 2 = output index 1)
public OutPoint(byte[] hash, int index) {
this.hash = hash;
this.index = index;
}
@Override
public String toString() {
return "{" + "\"hash\":\"" + BTCUtils.toHex(hash) + "\", \"index\":\"" + index + "\"}";
}
}
public static class Output {
public final long value;
public final Script script;
public Output(long value, Script script) {
this.value = value;
this.script = script;
}
@Override
public String toString() {
return "{\n\"value\":\"" + value * 1e-8 + "\",\"script\":\"" + script + "\"\n}";
}
}
public static final class Script {
public static class ScriptInvalidException extends Exception {
public ScriptInvalidException() {
}
public ScriptInvalidException(String s) {
super(s);
}
}
public static final byte OP_FALSE = 0;
public static final byte OP_TRUE = 0x51;
public static final byte OP_PUSHDATA1 = 0x4c;
public static final byte OP_PUSHDATA2 = 0x4d;
public static final byte OP_PUSHDATA4 = 0x4e;
public static final byte OP_DUP = 0x76;//Duplicates the top stack item.
public static final byte OP_DROP = 0x75;
public static final byte OP_HASH160 = (byte) 0xA9;//The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
public static final byte OP_VERIFY = 0x69;//Marks transaction as invalid if top stack value is not true. True is removed, but false is not.
public static final byte OP_EQUAL = (byte) 0x87;//Returns 1 if the inputs are exactly equal, 0 otherwise.
public static final byte OP_EQUALVERIFY = (byte) 0x88;//Same as OP_EQUAL, but runs OP_VERIFY afterward.
public static final byte OP_CHECKSIG = (byte) 0xAC;//The entire transaction's outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise.
public static final byte OP_CHECKSIGVERIFY = (byte) 0xAD;
public static final byte OP_NOP = 0x61;
public static final byte SIGHASH_ALL = 1;
public final byte[] bytes;
public Script(byte[] rawBytes) {
bytes = rawBytes;
}
public Script(byte[] data1, byte[] data2) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(data1.length + data2.length + 2);
try {
writeBytes(data1, baos);
writeBytes(data2, baos);
baos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
bytes = baos.toByteArray();
}
private static void writeBytes(byte[] data, ByteArrayOutputStream baos) throws IOException {
if (data.length < OP_PUSHDATA1) {
baos.write(data.length);
} else if (data.length < 0xff) {
baos.write(OP_PUSHDATA1);
baos.write(data.length);
} else if (data.length < 0xffff) {
baos.write(OP_PUSHDATA2);
baos.write(data.length & 0xff);
baos.write((data.length >> 8) & 0xff);
} else {
baos.write(OP_PUSHDATA4);
baos.write(data.length & 0xff);
baos.write((data.length >> 8) & 0xff);
baos.write((data.length >> 16) & 0xff);
baos.write((data.length >>> 24) & 0xff);
}
baos.write(data);
}
public void run(Stack<byte[]> stack) throws ScriptInvalidException {
run(0, null, stack);
}
public void run(int inputIndex, Transaction tx, Stack<byte[]> stack) throws ScriptInvalidException {
for (int pos = 0; pos < bytes.length; pos++) {
switch (bytes[pos]) {
case OP_NOP:
break;
case OP_DROP:
if (stack.isEmpty()) {
throw new IllegalArgumentException("stack empty on OP_DROP");
}
stack.pop();
break;
case OP_DUP:
if (stack.isEmpty()) {
throw new IllegalArgumentException("stack empty on OP_DUP");
}
stack.push(stack.peek());
break;
case OP_HASH160:
if (stack.isEmpty()) {
throw new IllegalArgumentException("stack empty on OP_HASH160");
}
stack.push(CryptoUtil.sha256ripemd160(stack.pop()));
break;
case OP_EQUAL:
case OP_EQUALVERIFY:
if (stack.size() < 2) {
throw new IllegalArgumentException("not enough elements to perform OP_EQUAL");
}
stack.push(new byte[]{(byte) (Arrays.equals(stack.pop(), stack.pop()) ? 1 : 0)});
if (bytes[pos] == OP_EQUALVERIFY) {
if (verifyFails(stack)) {
throw new ScriptInvalidException("wrong address");
}
}
break;
case OP_VERIFY:
if (verifyFails(stack)) {
throw new ScriptInvalidException();
}
break;
case OP_CHECKSIG:
case OP_CHECKSIGVERIFY:
byte[] publicKey = stack.pop();
byte[] signatureAndHashType = stack.pop();
if (signatureAndHashType[signatureAndHashType.length - 1] != SIGHASH_ALL) {
throw new IllegalArgumentException("I cannot check this sig type: " + signatureAndHashType[signatureAndHashType.length - 1]);
}
byte[] signature = new byte[signatureAndHashType.length - 1];
System.arraycopy(signatureAndHashType, 0, signature, 0, signature.length);
byte[] hash = hashTransaction(inputIndex, bytes, tx);
//boolean valid = BTCUtils.verify(publicKey, signature, hash);
if (bytes[pos] == OP_CHECKSIG) {
stack.push(new byte[]{(byte) (1)});
} else {
if (verifyFails(stack)) {
throw new ScriptInvalidException("Bad signature");
}
if (!stack.empty()) {
throw new ScriptInvalidException("Bad signature - superfluous scriptSig operations");
}
}
break;
case OP_FALSE:
stack.push(new byte[]{0});
break;
case OP_TRUE:
stack.push(new byte[]{1});
break;
default:
int op = bytes[pos] & 0xff;
int len;
if (op < OP_PUSHDATA1) {
len = op;
byte[] data = new byte[len];
System.arraycopy(bytes, pos + 1, data, 0, len);
stack.push(data);
pos += data.length;
} else if (op == OP_PUSHDATA1) {
len = bytes[pos + 1] & 0xff;
byte[] data = new byte[len];
System.arraycopy(bytes, pos + 1, data, 0, len);
stack.push(data);
pos += 1 + data.length;
} else {
throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos]));
}
break;
}
}
}
public static byte[] hashTransaction(int inputIndex, byte[] subscript, Transaction tx) {
Input[] unsignedInputs = new Input[tx.inputs.length];
for (int i = 0; i < tx.inputs.length; i++) {
Input txInput = tx.inputs[i];
if (i == inputIndex) {
unsignedInputs[i] = new Input(txInput.outPoint, new Script(subscript), txInput.sequence);
} else {
unsignedInputs[i] = new Input(txInput.outPoint, new Script(new byte[0]), txInput.sequence);
}
}
Transaction unsignedTransaction = new Transaction(unsignedInputs, tx.outputs, tx.lockTime);
return hashTransactionForSigning(unsignedTransaction);
}
public static byte[] hashTransactionForSigning(Transaction unsignedTransaction) {
byte[] txUnsignedBytes = unsignedTransaction.getBytes();
BitcoinOutputStream baos = new BitcoinOutputStream();
try {
baos.write(txUnsignedBytes);
baos.writeInt32(Script.SIGHASH_ALL);
baos.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
return CryptoUtil.doubleSha256(baos.toByteArray());
}
public static boolean verifyFails(Stack<byte[]> stack) {
byte[] input;
boolean valid;
input = stack.pop();
if (input.length == 0 || (input.length == 1 && input[0] == OP_FALSE)) {
//false
stack.push(new byte[]{OP_FALSE});
valid = false;
} else {
//true
valid = true;
}
return !valid;
}
@Override
public String toString() {
return convertBytesToReadableString(bytes);
}
//converts something like "OP_DUP OP_HASH160 ba507bae8f1643d2556000ca26b9301b9069dc6b OP_EQUALVERIFY OP_CHECKSIG" into bytes
public static byte[] convertReadableStringToBytes(String readableString) {
String[] tokens = readableString.trim().split("\\s+");
ByteArrayOutputStream os = new ByteArrayOutputStream();
for (String token : tokens) {
switch (token) {
case "OP_NOP":
os.write(OP_NOP);
break;
case "OP_DROP":
os.write(OP_DROP);
break;
case "OP_DUP":
os.write(OP_DUP);
break;
case "OP_HASH160":
os.write(OP_HASH160);
break;
case "OP_EQUAL":
os.write(OP_EQUAL);
break;
case "OP_EQUALVERIFY":
os.write(OP_EQUALVERIFY);
break;
case "OP_VERIFY":
os.write(OP_VERIFY);
break;
case "OP_CHECKSIG":
os.write(OP_CHECKSIG);
break;
case "OP_CHECKSIGVERIFY":
os.write(OP_CHECKSIGVERIFY);
break;
case "OP_FALSE":
os.write(OP_FALSE);
break;
case "OP_TRUE":
os.write(OP_TRUE);
break;
default:
if (token.startsWith("OP_")) {
throw new IllegalArgumentException("I don't know this operation: " + token);
}
byte[] data = BTCUtils.fromHex(token);
if (data == null) {
throw new IllegalArgumentException("I don't know what's this: " + token);
}
if (data.length < OP_PUSHDATA1) {
os.write(data.length);
try {
os.write(data);
} catch (IOException e) {
throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e);
}
} else if (data.length <= 255) {
os.write(OP_PUSHDATA1);
os.write(data.length);
try {
os.write(data);
} catch (IOException e) {
throw new RuntimeException("ByteArrayOutputStream behaves weird: " + e);
}
} else {
throw new IllegalArgumentException("OP_PUSHDATA2 & OP_PUSHDATA4 are not supported");
}
break;
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
return os.toByteArray();
}
public static String convertBytesToReadableString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int pos = 0; pos < bytes.length; pos++) {
if (sb.length() > 0) {
sb.append(' ');
}
switch (bytes[pos]) {
case OP_NOP:
sb.append("OP_NOP");
break;
case OP_DROP:
sb.append("OP_DROP");
break;
case OP_DUP:
sb.append("OP_DUP");
break;
case OP_HASH160:
sb.append("OP_HASH160");
break;
case OP_EQUAL:
sb.append("OP_EQUAL");
break;
case OP_EQUALVERIFY:
sb.append("OP_EQUALVERIFY");
break;
case OP_VERIFY:
sb.append("OP_VERIFY");
break;
case OP_CHECKSIG:
sb.append("OP_CHECKSIG");
break;
case OP_CHECKSIGVERIFY:
sb.append("OP_CHECKSIGVERIFY");
break;
case OP_FALSE:
sb.append("OP_FALSE");
break;
case OP_TRUE:
sb.append("OP_TRUE");
break;
default:
int op = bytes[pos] & 0xff;
int len;
if (op < OP_PUSHDATA1) {
len = op;
byte[] data = new byte[len];
System.arraycopy(bytes, pos + 1, data, 0, len);
sb.append(BTCUtils.toHex(data));
pos += data.length;
} else if (op == OP_PUSHDATA1) {
len = bytes[pos + 1] & 0xff;
byte[] data = new byte[len];
System.arraycopy(bytes, pos + 1, data, 0, len);//FIXME I suspect there is off by one error...
sb.append(BTCUtils.toHex(data));
pos += 1 + data.length;
} else {
throw new IllegalArgumentException("I cannot read this data: " + Integer.toHexString(bytes[pos]) + " at " + pos);
}
break;
}
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
return this == o || !(o == null || getClass() != o.getClass()) && Arrays.equals(bytes, ((Script) o).bytes);
}
@Override
public int hashCode() {
return Arrays.hashCode(bytes);
}
public static Script buildOutput(String address) throws BitcoinException {
//noinspection TryWithIdenticalCatches
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111) {
return buildOutputP2H(address);
}
if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4) {
return buildOutputP2SH(address);
}
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
}
public static Script buildOutputP2SH(String address) throws BitcoinException {
try {
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4) {
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
}
byte[] bareAddress = new byte[20];
System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length);
ByteArrayOutputStream buf = new ByteArrayOutputStream(23);
buf.write(OP_HASH160);
writeBytes(bareAddress, buf);
buf.write(OP_EQUAL);
return new Script(buf.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Script buildOutputP2H(String address) throws BitcoinException {
//noinspection TryWithIdenticalCatches
try {
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111) {
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
}
byte[] bareAddress = new byte[20];
System.arraycopy(addressWithCheckSumAndNetworkCode, 1, bareAddress, 0, bareAddress.length);
MessageDigest digestSha = MessageDigest.getInstance("SHA-256");
digestSha.update(addressWithCheckSumAndNetworkCode, 0, addressWithCheckSumAndNetworkCode.length - 4);
byte[] calculatedDigest = digestSha.digest(digestSha.digest());
for (int i = 0; i < 4; i++) {
if (calculatedDigest[i] != addressWithCheckSumAndNetworkCode[addressWithCheckSumAndNetworkCode.length - 4 + i]) {
throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Bad address", address);
}
}
ByteArrayOutputStream buf = new ByteArrayOutputStream(25);
buf.write(OP_DUP);
buf.write(OP_HASH160);
writeBytes(bareAddress, buf);
buf.write(OP_EQUALVERIFY);
buf.write(OP_CHECKSIG);
return new Script(buf.toByteArray());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
@SuppressWarnings("WeakerAccess")
public class UnspentOutputInfo {
public final byte[] txHash;
public final Transaction.Script script;
public final long value;
public final int outputIndex;
public final long confirmations;
public String txHashForBuild;
public byte[] scriptForBuild;
public byte[] bodyDoubleHash;
public byte[] bodyHash;
public UnspentOutputInfo(byte[] txHash, Transaction.Script script, long value, int outputIndex, long confirmations, String hashForBuild, byte[] sign) {
this.txHash = txHash;
this.script = script;
this.value = value;
this.outputIndex = outputIndex;
this.confirmations = confirmations;
this.txHashForBuild = hashForBuild;
this.scriptForBuild = sign;
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class VerifyCardActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_verify_card);
MainActivity.commonInit(getApplicationContext());
}
@Override
public void onBackPressed() {
//super.onBackPressed();
VerifyCardActivityFragment verifyCardActivityFragment= (VerifyCardActivityFragment) getSupportFragmentManager().findFragmentById(R.id.verify_card_fragment);
Intent data= verifyCardActivityFragment.prepareResultIntent();
data.putExtra("modification", "update");
setResult(Activity.RESULT_OK, data);
finish();
}
}

View file

@ -0,0 +1,360 @@
package com.tangem.wallet;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.tangem.cardReader.NfcManager;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class VerifyCardActivityFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, NfcAdapter.ReaderCallback {
Tangem_Card mCard;
TextView tvCardID, tvManufacturer, tvRegistrationDate, tvCardIdentity, tvLastSigned, tvRemainingSignatures, tvReusable, tvOk, tvError, tvMessage,
tvIssuer, tvIssuerData, tvFeatures, tvBlockchain, tvSignedTx, tvSigningMethod, tvFirmware, tvWalletIdentity, tvWallet;
ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion;
SwipeRefreshLayout mSwipeRefreshLayout;
private NfcManager mNfcManager;
public VerifyCardActivityFragment() {
}
public void onRefresh() {
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_verify_card, container, false);
mNfcManager = new NfcManager(this.getActivity(), this);
// SwipeRefreshLayout
mSwipeRefreshLayout = v.findViewById(R.id.swipe_container);
mSwipeRefreshLayout.setOnRefreshListener(this);
mCard = new Tangem_Card(getActivity().getIntent().getStringExtra("UID"));
mCard.LoadFromBundle(getActivity().getIntent().getExtras().getBundle("Card"));
tvCardID = v.findViewById(R.id.tvCardID);
tvLastSigned = v.findViewById(R.id.tvLastSigned);
tvRemainingSignatures = v.findViewById(R.id.tvRemainingSignatures);
tvReusable = v.findViewById(R.id.tvReusable);
tvManufacturer = v.findViewById(R.id.tvManufacturerInfo);
tvCardIdentity = v.findViewById(R.id.tvCardIdentity);
tvRegistrationDate = v.findViewById(R.id.tvCardRegistredDate);
ivBlockchain = v.findViewById(R.id.imgBlockchain);
ivPIN = v.findViewById(R.id.imgPIN);
ivPIN2orSecurityDelay = v.findViewById(R.id.imgPIN2orSecurityDelay);
ivDeveloperVersion = v.findViewById(R.id.imgDeveloperVersion);
tvError = v.findViewById(R.id.tvError);
tvMessage = v.findViewById(R.id.tvMessage);
tvIssuer = v.findViewById(R.id.tvIssuer);
tvIssuerData = v.findViewById(R.id.tvIssuerData);
tvFirmware = v.findViewById(R.id.tvFirmware);
tvFeatures = v.findViewById(R.id.tvFeatures);
tvBlockchain = v.findViewById(R.id.tvBlockchain);
tvSignedTx = v.findViewById(R.id.tvSignedTx);
tvSigningMethod = v.findViewById(R.id.tvSigningMethod);
tvOk = v.findViewById(R.id.tvOk);
if (tvOk != null) {
tvOk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent data = prepareResultIntent();
data.putExtra("modification", "update");
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().finish();
}
});
}
tvWallet = v.findViewById(R.id.tvWallet);
tvWalletIdentity = v.findViewById(R.id.tvWalletIdentity);
UpdateViews();
// if (NeedUpdate) {
// mSwipeRefreshLayout.setRefreshing(true);
// mSwipeRefreshLayout.postDelayed(new Runnable() {
// @Override
// public void run() {
// onRefresh();
// }
// }, 1000);
// }
return v;
}
void UpdateViews() {
try {
if (timerHideErrorAndMessage != null) {
timerHideErrorAndMessage.cancel();
timerHideErrorAndMessage = null;
}
tvCardID.setText(mCard.getCIDDescription());
if (mCard.getError() == null || mCard.getError().isEmpty()) {
tvError.setVisibility(View.GONE);
tvError.setText("");
} else {
tvError.setVisibility(View.VISIBLE);
tvError.setText(mCard.getError());
}
if (mCard.getMessage() == null || mCard.getMessage().isEmpty()) {
tvMessage.setVisibility(View.GONE);
tvMessage.setText("");
} else {
tvMessage.setVisibility(View.VISIBLE);
tvMessage.setText(mCard.getMessage());
}
tvManufacturer.setText(mCard.getManufacturer().getOfficialName());
if (mCard.isManufacturerConfirmed() && mCard.isCardPublicKeyValid()) {
tvCardIdentity.setText("Attested");
tvCardIdentity.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme()));
} else {
tvCardIdentity.setText("Not confirmed");
tvCardIdentity.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme()));
}
tvIssuer.setText(mCard.getIssuerDescription());
tvIssuerData.setText(mCard.getIssuerDataDescription());
tvRegistrationDate.setText(mCard.getPersonalizationDateTimeDescription());
//tvBlockchain.setText(mCard.getBlockchain().getOfficialName());
tvBlockchain.setText(mCard.getBlockchainName());
ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this.getContext(), mCard.getTokenSymbol()));
if (mCard.isReusable()) {
tvReusable.setText("Reusable");
} else {
tvReusable.setText("One-off banknote");
}
tvSigningMethod.setText(mCard.getSigningMethod().getDescription());
if (mCard.getStatus() == Tangem_Card.Status.Loaded || mCard.getStatus() == Tangem_Card.Status.Purged) {
tvLastSigned.setText(mCard.getLastSignedDescription());
if (mCard.getRemainingSignatures() == 0) {
tvRemainingSignatures.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme()));
tvRemainingSignatures.setText("None");
} else if (mCard.getRemainingSignatures() == 1) {
tvRemainingSignatures.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme()));
tvRemainingSignatures.setText("Last one!");
} else if (mCard.getRemainingSignatures() > 1000) {
tvRemainingSignatures.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme()));
tvRemainingSignatures.setText("Unlimited");
} else {
tvRemainingSignatures.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme()));
tvRemainingSignatures.setText(String.valueOf(mCard.getRemainingSignatures()));
}
tvSignedTx.setText(String.valueOf(mCard.getMaxSignatures() - mCard.getRemainingSignatures()));
} else {
tvLastSigned.setText("");
tvRemainingSignatures.setText("");
tvSignedTx.setText("");
}
tvFirmware.setText(mCard.getFirmwareVersion());
String features = "";
if (mCard.allowSwapPIN() && mCard.allowSwapPIN2()) {
features += "Allows change PIN1 and PIN2\n";
} else if (mCard.allowSwapPIN()) {
features += "Allows change PIN1\n";
} else if (mCard.allowSwapPIN2()) {
features += "Allows change PIN2\n";
} else {
features += "Fixed PIN1 and PIN2\n";
}
if (mCard.needCVC()) {
features += "Requires CVC\n";
}
if (mCard.supportDynamicNDEF()) {
features += "Dynamic NDEF for iOS\n";
} else if (mCard.supportNDEF()) {
features += "NDEF\n";
}
if (mCard.supportBlock()) {
features += "Blockable\n";
}
if (mCard.supportOnlyOneCommandAtTime()) {
features += "Atomic command mode";
}
if (features.endsWith("\n")) {
features = features.substring(0, features.length() - 1);
}
tvFeatures.setText(features);
if (mCard.useDefaultPIN1()) {
ivPIN.setImageResource(R.drawable.unlock_pin1);
ivPIN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show();
}
});
} else {
ivPIN.setImageResource(R.drawable.lock_pin1);
ivPIN.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show();
}
});
}
if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) {
ivPIN2orSecurityDelay.setImageResource(R.drawable.timer);
ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show();
}
});
} else if (mCard.useDefaultPIN2()) {
ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2);
ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show();
}
});
} else {
ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2);
ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show();
}
});
}
if (mCard.useDevelopersFirmware()) {
ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version);
ivDeveloperVersion.setVisibility(View.VISIBLE);
ivDeveloperVersion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show();
}
});
} else {
ivDeveloperVersion.setVisibility(View.INVISIBLE);
}
if (mCard.getStatus() == Tangem_Card.Status.Loaded) {
tvWallet.setText(mCard.getShortWalletString());
if (mCard.isWalletPublicKeyValid()) {
tvWalletIdentity.setText("Possession proved");
tvWalletIdentity.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme()));
} else {
tvWalletIdentity.setText("Possession NOT proved");
tvWalletIdentity.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme()));
}
} else {
tvWallet.setText("not available");
tvWalletIdentity.setText("-- -- --");
}
timerHideErrorAndMessage = new Timer();
timerHideErrorAndMessage.schedule(new TimerTask() {
@Override
public void run() {
tvError.post(new Runnable() {
@Override
public void run() {
tvMessage.setVisibility(View.GONE);
tvError.setVisibility(View.GONE);
mCard.setError(null);
mCard.setMessage(null);
}
});
}
}, 5000);
} catch (Exception e) {
e.printStackTrace();
}
}
Timer timerHideErrorAndMessage = null;
public Intent prepareResultIntent() {
Intent data = new Intent();
data.putExtra("UID", mCard.getUID());
data.putExtra("Card", mCard.getAsBundle());
return data;
}
@Override
public void onResume() {
super.onResume();
mNfcManager.onResume();
}
@Override
public void onPause() {
super.onPause();
mNfcManager.onPause();
}
@Override
public void onStop() {
super.onStop();
mNfcManager.onStop();
}
@Override
public void onTagDiscovered(Tag tag) {
try {
Log.w(getClass().getName(), "Ignore discovered tag!");
mNfcManager.IgnoreTag(tag);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,109 @@
package com.tangem.wallet;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.cardReader.CardProtocol;
import com.tangem.cardReader.NfcManager;
/**
* Created by dvol on 04.02.2018.
*/
public class VerifyCardTask extends Thread {
IsoDep mIsoDep;
CardProtocol.Notifications mNotifications;
private final String logTag = "VerifyCardTask";
private boolean isCancelled = false;
private Context mContext;
private Tangem_Card mCard;
private NfcManager mNfcManager;
VerifyCardTask(Context context, Tangem_Card 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("VerifyCardTask", "[-- Start verify card --]");
if (isCancelled) return;
String PIN = mCard.getPIN();
protocol.setPIN(PIN);
protocol.run_Read();
PINStorage.setLastUsedPIN(PIN);
mNotifications.OnReadProgress(protocol, 30);
if (isCancelled) return;
protocol.run_VerifyCard();
mNotifications.OnReadProgress(protocol, 60);
Log.i("VerifyCardTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName());
if (isCancelled) return;
if (protocol.getCard().getStatus() == Tangem_Card.Status.Loaded) {
protocol.run_CheckWalletWithSignatureVerify();
mNotifications.OnReadProgress(protocol, 90);
}
// if (isCancelled) return;
// if (protocol.getCard().getStatus() == Tangem_Card.Status.Loaded) {
// protocol.run_CheckWithSignatureVerify();
// }
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i("VerifyCardTask", "[-- Finish verify card --]");
mNotifications.OnReadFinish(protocol);
}
} finally {
mNfcManager.IgnoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
}
}
void cancel(Boolean AllowInterrupt) {
try {
if (this.isAlive()) {
isCancelled = true;
join(500);
}
if (this.isAlive() && AllowInterrupt) {
interrupt();
mNotifications.OnReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,70 @@
package com.tangem.wallet;
import android.content.Context;
import android.graphics.Canvas;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.TextView;
public class VerticalTextView extends TextView
{
final boolean topDown;
public VerticalTextView( Context context,
AttributeSet attrs )
{
super( context, attrs );
final int gravity = getGravity();
if ( Gravity.isVertical( gravity )
&& ( gravity & Gravity.VERTICAL_GRAVITY_MASK )
== Gravity.BOTTOM )
{
setGravity(
( gravity & Gravity.HORIZONTAL_GRAVITY_MASK )
| Gravity.TOP );
topDown = false;
}
else
{
topDown = true;
}
}
@Override
protected void onMeasure( int widthMeasureSpec,
int heightMeasureSpec )
{
super.onMeasure( heightMeasureSpec,
widthMeasureSpec );
setMeasuredDimension( getMeasuredHeight(),
getMeasuredWidth() );
}
@Override
protected void onDraw( Canvas canvas )
{
TextPaint textPaint = getPaint();
textPaint.setColor( getCurrentTextColor() );
textPaint.drawableState = getDrawableState();
canvas.save();
if ( topDown )
{
canvas.translate( getWidth(), 0 );
canvas.rotate( 90 );
}
else
{
canvas.translate( 0, getHeight() );
canvas.rotate( -90 );
}
canvas.translate( getCompoundPaddingLeft(),
getExtendedPaddingTop() );
getLayout().draw( canvas );
canvas.restore();
}
}

View file

@ -0,0 +1,166 @@
package com.tangem.wallet;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by dvol on 06.03.2018.
*/
public class WaitSecurityDelayDialog extends DialogFragment {
ProgressBar progressBar;
int msTimeout = 60000, msProgress = 0;
Timer timer;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
View v = inflater.inflate(R.layout.dialog_wait_pin2, null);
progressBar = v.findViewById(R.id.progressBar);
progressBar.setMax(msTimeout);
progressBar.setProgress(msProgress);
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
progressBar.post(new Runnable() {
@Override
public void run() {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) {
WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000);
}
}
});
}
}, 1000, 1000);
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle("Security delay")
.setView(v)
.setCancelable(false)
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
}
public void setup(int msTimeout, int msProgress) {
this.msTimeout = msTimeout;
this.msProgress = msProgress;
}
public void setRemainingTimeout(final int msec) {
progressBar.post(new Runnable() {
@Override
public void run() {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
progressBar.setMax(progress + msec);
timer.cancel();
timer = null;
} else {
int newProgress = progressBar.getMax() - msec;
if (newProgress > progress) {
progressBar.setProgress(newProgress);
} else {
progressBar.setMax(progress + msec);
}
}
}
});
}
static Timer timerToShowDelayDialog = null;
static WaitSecurityDelayDialog instance = null;
public static WaitSecurityDelayDialog getInstance() {
if (instance == null) {
instance = new WaitSecurityDelayDialog();
}
return instance;
}
private final static int MinRemainingDelayToShowDialog=1000;
private final static int DelayBeforeShowDialog=5000;
public static void onReadBeforeRequest(final Activity activity, final int timeout) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog+MinRemainingDelayToShowDialog) return;
timerToShowDelayDialog = new Timer();
timerToShowDelayDialog.schedule(new TimerTask() {
@Override
public void run() {
if (WaitSecurityDelayDialog.instance != null) return;
instance = new WaitSecurityDelayDialog();
instance.setup(timeout, DelayBeforeShowDialog);
instance.setCancelable(false);
instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog");
}
}, DelayBeforeShowDialog);
}
});
}
public static void onReadAfterRequest(final Activity activity) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (timerToShowDelayDialog == null) return;
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
}
});
}
public static void OnReadWait(final Activity activity, final int msec) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (timerToShowDelayDialog != null) {
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
}
if (msec == 0) {
if (instance != null) {
instance.dismiss();
instance = null;
}
return;
}
if (instance == null) {
if( msec>MinRemainingDelayToShowDialog ) {
instance = new WaitSecurityDelayDialog();
// 1000ms - card delay notification interval
instance.setup(msec + 1000, 1000);
instance.setCancelable(false);
instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog");
}
} else {
instance.setRemainingTimeout(msec);
}
}
});
}
}

View file

@ -0,0 +1,151 @@
package com.tangem.wallet;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.util.Hashtable;
import static android.content.Context.CLIPBOARD_SERVICE;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link WalletInfoFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class WalletInfoFragment extends Fragment {
// TODO: Rename and change types of parameters
private Tangem_Card mCard;
private OnFragmentInteractionListener mListener;
public WalletInfoFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment WalletInfoFragment.
*/
// TODO: Rename and change types and number of parameters
public static WalletInfoFragment newInstance(Tangem_Card card) {
WalletInfoFragment fragment = new WalletInfoFragment();
Bundle args = new Bundle();
args.putString("UID",card.getUID());
card.SaveToBundle(args);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mCard = new Tangem_Card(getArguments().getString("UID"));
mCard.LoadFromBundle(getArguments());
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View result=inflater.inflate(R.layout.fragment_wallet_info, container, false);
ImageView mImage= (ImageView)result.findViewById(R.id.qrWallet);
try {
mImage.setImageBitmap(generateQrCode(mCard.getWallet()));
} catch (WriterException e) {
e.printStackTrace();
}
TextView mText=(TextView)result.findViewById(R.id.strWallet);
mText.setText(mCard.getWallet());
mText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TextView mText = (TextView) view;
ClipboardManager clipboard = (ClipboardManager)getActivity().getSystemService(CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText(mText.getText(), mText.getText()));
Toast.makeText(getContext(),"Copied to clipboard",Toast.LENGTH_LONG).show();
}
});
return result;
}
public static Bitmap generateQrCode(String myCodeText) throws WriterException {
Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<EncodeHintType, ErrorCorrectionLevel>();
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage
QRCodeWriter qrCodeWriter = new QRCodeWriter();
int size = 256;
BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hintMap);
int width = bitMatrix.getWidth();
Bitmap bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565);
for (int x = 0; x < width; x++) {
for (int y = 0; y < width; y++) {
bmp.setPixel(y, x, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE);
}
}
return bmp;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
// void onFragmentInteraction(Uri uri);
}
}