Updated on 2026-08-14
This commit is contained in:
commit
5e7300987c
243 changed files with 28116 additions and 0 deletions
519
app/src/main/java/com/tangem/wallet/BTCUtils.java
Normal file
519
app/src/main/java/com/tangem/wallet/BTCUtils.java
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
115
app/src/main/java/com/tangem/wallet/Base58.java
Normal file
115
app/src/main/java/com/tangem/wallet/Base58.java
Normal 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();
|
||||
}
|
||||
}
|
||||
35
app/src/main/java/com/tangem/wallet/BitcoinException.java
Normal file
35
app/src/main/java/com/tangem/wallet/BitcoinException.java
Normal 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);
|
||||
}
|
||||
}
|
||||
69
app/src/main/java/com/tangem/wallet/BitcoinInputStream.java
Normal file
69
app/src/main/java/com/tangem/wallet/BitcoinInputStream.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
42
app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java
Normal file
42
app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
app/src/main/java/com/tangem/wallet/Blockchain.java
Normal file
99
app/src/main/java/com/tangem/wallet/Blockchain.java
Normal 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;
|
||||
}
|
||||
}
|
||||
428
app/src/main/java/com/tangem/wallet/BtcCashEngine.java
Normal file
428
app/src/main/java/com/tangem/wallet/BtcCashEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
564
app/src/main/java/com/tangem/wallet/BtcEngine.java
Normal file
564
app/src/main/java/com/tangem/wallet/BtcEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/com/tangem/wallet/ByteUtil.java
Normal file
39
app/src/main/java/com/tangem/wallet/ByteUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
122
app/src/main/java/com/tangem/wallet/CardInfoActivity.java
Normal file
122
app/src/main/java/com/tangem/wallet/CardInfoActivity.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
599
app/src/main/java/com/tangem/wallet/CardListAdapter.java
Normal file
599
app/src/main/java/com/tangem/wallet/CardListAdapter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
82
app/src/main/java/com/tangem/wallet/CoinEngine.java
Normal file
82
app/src/main/java/com/tangem/wallet/CoinEngine.java
Normal 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;
|
||||
|
||||
}
|
||||
23
app/src/main/java/com/tangem/wallet/CoinEngineFactory.java
Normal file
23
app/src/main/java/com/tangem/wallet/CoinEngineFactory.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
732
app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java
Normal file
732
app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
330
app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java
Normal file
330
app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
270
app/src/main/java/com/tangem/wallet/CryptoUtil.java
Normal file
270
app/src/main/java/com/tangem/wallet/CryptoUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
130
app/src/main/java/com/tangem/wallet/DerEncodingUtil.java
Normal file
130
app/src/main/java/com/tangem/wallet/DerEncodingUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
2091
app/src/main/java/com/tangem/wallet/DeviceName.java
Normal file
2091
app/src/main/java/com/tangem/wallet/DeviceName.java
Normal file
File diff suppressed because it is too large
Load diff
114
app/src/main/java/com/tangem/wallet/Digest.java
Normal file
114
app/src/main/java/com/tangem/wallet/Digest.java
Normal 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();
|
||||
}
|
||||
220
app/src/main/java/com/tangem/wallet/DigestEngine.java
Normal file
220
app/src/main/java/com/tangem/wallet/DigestEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
52
app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java
Normal file
52
app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
229
app/src/main/java/com/tangem/wallet/ETH_Transaction.java
Normal file
229
app/src/main/java/com/tangem/wallet/ETH_Transaction.java
Normal 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;
|
||||
}
|
||||
}
|
||||
184
app/src/main/java/com/tangem/wallet/Electrum_Request.java
Normal file
184
app/src/main/java/com/tangem/wallet/Electrum_Request.java
Normal 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");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
116
app/src/main/java/com/tangem/wallet/Electrum_Task.java
Normal file
116
app/src/main/java/com/tangem/wallet/Electrum_Task.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
356
app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java
Normal file
356
app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
392
app/src/main/java/com/tangem/wallet/EthEngine.java
Normal file
392
app/src/main/java/com/tangem/wallet/EthEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
92
app/src/main/java/com/tangem/wallet/ExchangeRequest.java
Normal file
92
app/src/main/java/com/tangem/wallet/ExchangeRequest.java
Normal 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");
|
||||
}
|
||||
}
|
||||
66
app/src/main/java/com/tangem/wallet/ExchangeTask.java
Normal file
66
app/src/main/java/com/tangem/wallet/ExchangeTask.java
Normal 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;
|
||||
}
|
||||
}
|
||||
102
app/src/main/java/com/tangem/wallet/Fee_Request.java
Normal file
102
app/src/main/java/com/tangem/wallet/Fee_Request.java
Normal 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");
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/com/tangem/wallet/Fee_Task.java
Normal file
63
app/src/main/java/com/tangem/wallet/Fee_Task.java
Normal 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;
|
||||
}
|
||||
}
|
||||
62
app/src/main/java/com/tangem/wallet/FingerprintHelper.java
Normal file
62
app/src/main/java/com/tangem/wallet/FingerprintHelper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/tangem/wallet/FormatUtil.java
Normal file
51
app/src/main/java/com/tangem/wallet/FormatUtil.java
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
197
app/src/main/java/com/tangem/wallet/Infura_Request.java
Normal file
197
app/src/main/java/com/tangem/wallet/Infura_Request.java
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
134
app/src/main/java/com/tangem/wallet/Infura_Task.java
Normal file
134
app/src/main/java/com/tangem/wallet/Infura_Task.java
Normal 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";
|
||||
}
|
||||
|
||||
}
|
||||
126
app/src/main/java/com/tangem/wallet/Issuer.java
Normal file
126
app/src/main/java/com/tangem/wallet/Issuer.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
39
app/src/main/java/com/tangem/wallet/Keccak256.java
Normal file
39
app/src/main/java/com/tangem/wallet/Keccak256.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
546
app/src/main/java/com/tangem/wallet/KeccakCore.java
Normal file
546
app/src/main/java/com/tangem/wallet/KeccakCore.java
Normal 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);
|
||||
}
|
||||
}
|
||||
146
app/src/main/java/com/tangem/wallet/LastSignStorage.java
Normal file
146
app/src/main/java/com/tangem/wallet/LastSignStorage.java
Normal 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 "";
|
||||
}
|
||||
}
|
||||
|
|
@ -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
108
app/src/main/java/com/tangem/wallet/LogFileProvider.java
Normal file
108
app/src/main/java/com/tangem/wallet/LogFileProvider.java
Normal 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;
|
||||
}
|
||||
}
|
||||
285
app/src/main/java/com/tangem/wallet/Logger.java
Normal file
285
app/src/main/java/com/tangem/wallet/Logger.java
Normal 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
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
68
app/src/main/java/com/tangem/wallet/LogoActivity.java
Normal file
68
app/src/main/java/com/tangem/wallet/LogoActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
404
app/src/main/java/com/tangem/wallet/MainActivity.java
Normal file
404
app/src/main/java/com/tangem/wallet/MainActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
1038
app/src/main/java/com/tangem/wallet/MainActivityFragment.java
Normal file
1038
app/src/main/java/com/tangem/wallet/MainActivityFragment.java
Normal file
File diff suppressed because it is too large
Load diff
126
app/src/main/java/com/tangem/wallet/Manufacturer.java
Normal file
126
app/src/main/java/com/tangem/wallet/Manufacturer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
206
app/src/main/java/com/tangem/wallet/PINStorage.java
Normal file
206
app/src/main/java/com/tangem/wallet/PINStorage.java
Normal 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;
|
||||
}
|
||||
}
|
||||
35
app/src/main/java/com/tangem/wallet/PhoneUtility.java
Normal file
35
app/src/main/java/com/tangem/wallet/PhoneUtility.java
Normal 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();
|
||||
|
||||
}
|
||||
}
|
||||
237
app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java
Normal file
237
app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
333
app/src/main/java/com/tangem/wallet/PurgeActivity.java
Normal file
333
app/src/main/java/com/tangem/wallet/PurgeActivity.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
90
app/src/main/java/com/tangem/wallet/QRScanActivity.java
Normal file
90
app/src/main/java/com/tangem/wallet/QRScanActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
228
app/src/main/java/com/tangem/wallet/RLP.java
Normal file
228
app/src/main/java/com/tangem/wallet/RLP.java
Normal 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;
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/tangem/wallet/RLPElement.java
Normal file
12
app/src/main/java/com/tangem/wallet/RLPElement.java
Normal 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();
|
||||
}
|
||||
37
app/src/main/java/com/tangem/wallet/RLPList.java
Normal file
37
app/src/main/java/com/tangem/wallet/RLPList.java
Normal 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 + ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
531
app/src/main/java/com/tangem/wallet/RequestPINActivity.java
Normal file
531
app/src/main/java/com/tangem/wallet/RequestPINActivity.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
543
app/src/main/java/com/tangem/wallet/SavePINActivity.java
Normal file
543
app/src/main/java/com/tangem/wallet/SavePINActivity.java
Normal 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");
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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) {
|
||||
//
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
208
app/src/main/java/com/tangem/wallet/SendTransactionActivity.java
Normal file
208
app/src/main/java/com/tangem/wallet/SendTransactionActivity.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
app/src/main/java/com/tangem/wallet/SharedData.java
Normal file
22
app/src/main/java/com/tangem/wallet/SharedData.java
Normal 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
326
app/src/main/java/com/tangem/wallet/SwapPINActivity.java
Normal file
326
app/src/main/java/com/tangem/wallet/SwapPINActivity.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1365
app/src/main/java/com/tangem/wallet/Tangem_Card.java
Normal file
1365
app/src/main/java/com/tangem/wallet/Tangem_Card.java
Normal file
File diff suppressed because it is too large
Load diff
436
app/src/main/java/com/tangem/wallet/TokenEngine.java
Normal file
436
app/src/main/java/com/tangem/wallet/TokenEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
632
app/src/main/java/com/tangem/wallet/Transaction.java
Normal file
632
app/src/main/java/com/tangem/wallet/Transaction.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java
Normal file
28
app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java
Normal 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;
|
||||
}
|
||||
}
|
||||
28
app/src/main/java/com/tangem/wallet/VerifyCardActivity.java
Normal file
28
app/src/main/java/com/tangem/wallet/VerifyCardActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
109
app/src/main/java/com/tangem/wallet/VerifyCardTask.java
Normal file
109
app/src/main/java/com/tangem/wallet/VerifyCardTask.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
70
app/src/main/java/com/tangem/wallet/VerticalTextView.java
Normal file
70
app/src/main/java/com/tangem/wallet/VerticalTextView.java
Normal 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();
|
||||
}
|
||||
}
|
||||
166
app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java
Normal file
166
app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
151
app/src/main/java/com/tangem/wallet/WalletInfoFragment.java
Normal file
151
app/src/main/java/com/tangem/wallet/WalletInfoFragment.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue