Updated on 2026-08-14

This commit is contained in:
Tangem 2018-05-29 21:45:37 +03:00
parent 61c4f7047d
commit 52ce16bb09
86 changed files with 9207 additions and 9295 deletions

View file

@ -1,4 +0,0 @@
package com.tangem.domain;
public class Logic {
}

View file

@ -2,6 +2,8 @@ package com.tangem.domain.cardReader;
import android.util.Log;
import com.tangem.util.Util;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
@ -163,7 +165,7 @@ public class CardCrypto {
} else {
Log.e("cardCrypto","r-length:" + String.valueOf(rLength));
Log.e("cardCrypto","s-length:" + String.valueOf(sLength));
Log.e("cardCrypto","enc:" +Util.bytesToHex(enc));
Log.e("cardCrypto","enc:" + Util.bytesToHex(enc));
throw new Exception("unsupported r-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc));
}
if (sLength <= 32) {

View file

@ -6,11 +6,12 @@ import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.wallet.CoinEngine;
import com.tangem.wallet.CoinEngineFactory;
import com.tangem.wallet.Issuer;
import com.tangem.wallet.Tangem_Card;
import com.tangem.wallet.Manufacturer;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.CoinEngineFactory;
import com.tangem.domain.wallet.Issuer;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.Manufacturer;
import com.tangem.util.Util;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.interfaces.ECPublicKey;
@ -58,7 +59,7 @@ public class CardProtocol {
mError = error;
}
public Tangem_Card getCard() {
public TangemCard getCard() {
return mCard;
}
@ -88,7 +89,7 @@ public class CardProtocol {
}
}
protected Tangem_Card mCard;
protected TangemCard mCard;
protected Exception mError;
protected Context mContext;
@ -101,10 +102,10 @@ public class CardProtocol {
mIsoDep = isoDep;
mNotifications = notifications;
mPIN = null;
mCard = new Tangem_Card(Util.byteArrayToHexString(mIsoDep.getTag().getId()));
mCard = new TangemCard(Util.byteArrayToHexString(mIsoDep.getTag().getId()));
}
public CardProtocol(Context context, IsoDep isoDep, Tangem_Card card, Notifications notifications) {
public CardProtocol(Context context, IsoDep isoDep, TangemCard card, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
@ -164,7 +165,7 @@ public class CardProtocol {
byte[] sessionKey = null;
public void run_OpenSession(Tangem_Card.EncryptionMode encryptionMode) throws Exception {
public void run_OpenSession(TangemCard.EncryptionMode encryptionMode) throws Exception {
sessionKey = null;
try {
CommandApdu cmdApdu = new CommandApdu(CommandApdu.ISO_CLA, INS.OpenSession.Code, 0, encryptionMode.getP());
@ -280,7 +281,7 @@ public class CardProtocol {
// send command APDU, get response APDU, and display HEX data to user
private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception {
if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) {
if (mCard.encryptionMode != TangemCard.EncryptionMode.None) {
if (sessionKey == null) {
run_OpenSession(mCard.encryptionMode);
}
@ -307,7 +308,7 @@ public class CardProtocol {
}
throw e;
}
if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) {
if (mCard.encryptionMode != TangemCard.EncryptionMode.None) {
rspApdu = ResponseApdu.Decrypt(rsp, sessionKey);
} else {
rspApdu = new ResponseApdu(rsp);
@ -388,7 +389,7 @@ public class CardProtocol {
tlvIssuerData = null;
}
}
if (mCard.getStatus() == Tangem_Card.Status.Loaded) {
if (mCard.getStatus() == TangemCard.Status.Loaded) {
// try read offline balance data
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) {
mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value);
@ -413,13 +414,13 @@ public class CardProtocol {
public void parseReadResult() throws TangemException, NoSuchProviderException, NoSuchAlgorithmException {
TLV tlvStatus = readResult.getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(Tangem_Card.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
mCard.setStatus(TangemCard.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
TLV tlvCID = readResult.getTLV(TLV.Tag.TAG_CardID);
mCard.setCID(tlvCID.Value);
mCard.setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
mCard.setHealth(readResult.getTLV(TLV.Tag.TAG_Health).getAsInt());
if (mCard.getStatus() != Tangem_Card.Status.NotPersonalized) {
if (mCard.getStatus() != TangemCard.Status.NotPersonalized) {
try {
TLV tlvCardPubkicKey = readResult.getTLV(TLV.Tag.TAG_CardPublicKey);
if (tlvCardPubkicKey == null)
@ -514,7 +515,7 @@ public class CardProtocol {
}
if (mCard.getStatus() == Tangem_Card.Status.Loaded) {
if (mCard.getStatus() == TangemCard.Status.Loaded) {
TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey);
@ -544,7 +545,7 @@ public class CardProtocol {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
}
if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) {
if (mCard.getStatus() == TangemCard.Status.NotPersonalized) {
getCard().setManufacturer(Manufacturer.Unknown, false);
return null;
}
@ -649,12 +650,12 @@ public class CardProtocol {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
}
if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) {
if (mCard.getStatus() == TangemCard.Status.NotPersonalized) {
getCard().setManufacturer(Manufacturer.Unknown, false);
return;
}
if (readResult.getTagAsInt(TLV.Tag.TAG_Status) != Tangem_Card.Status.Loaded.getCode()) {
if (readResult.getTagAsInt(TLV.Tag.TAG_Status) != TangemCard.Status.Loaded.getCode()) {
throw new TangemException("Card must be loaded");
}
TLVList checkResult = run_CheckWallet();
@ -922,7 +923,7 @@ public class CardProtocol {
}
public void run_GetSupportedEncryption() throws Exception {
mCard.encryptionMode = Tangem_Card.EncryptionMode.None;
mCard.encryptionMode = TangemCard.EncryptionMode.None;
do {
CommandApdu rqApdu = StartPrepareCommand(INS.Read);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -930,10 +931,10 @@ public class CardProtocol {
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.NEED_ENCRYPTION)) {
if (mCard.encryptionMode == Tangem_Card.EncryptionMode.None) {
mCard.encryptionMode = Tangem_Card.EncryptionMode.Fast;
} else if (mCard.encryptionMode == Tangem_Card.EncryptionMode.Fast) {
mCard.encryptionMode = Tangem_Card.EncryptionMode.Strong;
if (mCard.encryptionMode == TangemCard.EncryptionMode.None) {
mCard.encryptionMode = TangemCard.EncryptionMode.Fast;
} else if (mCard.encryptionMode == TangemCard.EncryptionMode.Fast) {
mCard.encryptionMode = TangemCard.EncryptionMode.Strong;
} else {
throw new Exception("Can't get supported encryption methods");
}

View file

@ -1,5 +1,7 @@
package com.tangem.domain.cardReader;
import com.tangem.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;

View file

@ -4,6 +4,8 @@ import android.content.Context;
import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
import com.tangem.util.Util;
import java.io.IOException;
public class CustomCardReader implements Runnable {

View file

@ -1,5 +1,7 @@
package com.tangem.domain.cardReader;
import com.tangem.util.Util;
import java.io.ByteArrayInputStream;
import java.util.Arrays;

View file

@ -1,5 +1,7 @@
package com.tangem.domain.cardReader;
import com.tangem.util.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

View file

@ -6,6 +6,8 @@ package com.tangem.domain.cardReader;
import android.support.annotation.NonNull;
import com.tangem.util.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

View file

@ -1,958 +0,0 @@
package com.tangem.domain.cardReader;
import android.text.format.DateUtils;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;
public class Util {
public static String getSpaces(int length) {
StringBuilder buf = new StringBuilder(length);
for (int i = 0; i < length; i++) {
buf.append(" ");
}
return buf.toString();
}
public static String prettyPrintHex(String in, int indent, boolean wrapLines) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < in.length(); i++) {
char c = in.charAt(i);
buf.append(c);
int nextPos = i+1;
if (wrapLines && nextPos % 32 == 0 && nextPos != in.length()) {
buf.append("\n").append(getSpaces(indent));
} else if (nextPos % 2 == 0 && nextPos != in.length()) {
//buf.append(" ");
}
}
return buf.toString();
}
public static String prettyPrintHex(String in, int indent){
return prettyPrintHex(in, indent, true);
}
public static String prettyPrintHex(byte[] data, int indent) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data), indent, true);
}
public static String prettyPrintHex(byte[] data) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data), 0, true);
}
public static String prettyPrintHex(byte[] data, int startPos, int length) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data, startPos, length), 0, true);
}
public static String prettyPrintHexNoWrap(byte[] data) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data), 0, false);
}
public static String prettyPrintHexNoWrap(byte[] data, int startPos, int length) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data, startPos, length), 0, false);
}
public static String prettyPrintHexNoWrap(String in) {
return Util.prettyPrintHex(in, 0, false);
}
public static String prettyPrintHex(String in) {
return prettyPrintHex(in, 0, true);
}
public static String prettyPrintHex(BigInteger bi) {
byte[] data = bi.toByteArray();
if (data[0] == (byte) 0x00) {
byte[] tmp = new byte[data.length - 1];
System.arraycopy(data, 1, tmp, 0, data.length - 1);
data = tmp;
}
return prettyPrintHex(data);
}
public static byte[] performRSA(byte[] dataBytes, byte[] expBytes, byte[] modBytes) {
int inBytesLength = dataBytes.length;
if (expBytes[0] >= (byte) 0x80) {
//Prepend 0x00 to modulus
byte[] tmp = new byte[expBytes.length + 1];
tmp[0] = (byte) 0x00;
System.arraycopy(expBytes, 0, tmp, 1, expBytes.length);
expBytes = tmp;
}
if (modBytes[0] >= (byte) 0x80) {
//Prepend 0x00 to modulus
byte[] tmp = new byte[modBytes.length + 1];
tmp[0] = (byte) 0x00;
System.arraycopy(modBytes, 0, tmp, 1, modBytes.length);
modBytes = tmp;
}
if (dataBytes[0] >= (byte) 0x80) {
//Prepend 0x00 to signed data to avoid that the most significant bit is interpreted as the "signed" bit
byte[] tmp = new byte[dataBytes.length + 1];
tmp[0] = (byte) 0x00;
System.arraycopy(dataBytes, 0, tmp, 1, dataBytes.length);
dataBytes = tmp;
}
BigInteger exp = new BigInteger(expBytes);
BigInteger mod = new BigInteger(modBytes);
BigInteger data = new BigInteger(dataBytes);
byte[] result = data.modPow(exp, mod).toByteArray();
if (result.length == (inBytesLength+1) && result[0] == (byte)0x00) {
//Remove 0x00 from beginning of array
byte[] tmp = new byte[inBytesLength];
System.arraycopy(result, 1, tmp, 0, inBytesLength);
result = tmp;
}
return result;
}
public static byte[] calculateSHA1(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
return sha1.digest(data);
}
public static byte[] calculateSHA224(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-224");
return sha.digest(data);
}
public static byte[] calculateSHA256(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
return sha256.digest(data);
}
public static byte[] calculateSHA384(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-384");
return sha.digest(data);
}
public static byte[] calculateSHA512(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-512");
return sha.digest(data);
}
public static byte[] calculateSHA256(String Message) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte data[]=Message.getBytes(Charset.forName("UTF-8"));
return sha256.digest(data);
}
public static byte[] calculateRIPEMD160(byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException {
//MessageDigest hashAlg = MessageDigest.getInstance("RIPEMD-160", "SC");
//return hashAlg.digest(data);
RIPEMD160Digest digest = new RIPEMD160Digest();
digest.update(data, 0, data.length);
byte[] out = new byte[20];
digest.doFinal(out, 0);
return out;
}
public static String byte2Hex(byte b) {
String[] HEX_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
int nb = b & 0xFF;
int i_1 = (nb >>> 4) & 0xF;
int i_2 = nb & 0xF;
return HEX_DIGITS[i_1] + HEX_DIGITS[i_2];
}
public static String short2Hex(short s) {
byte b1 = (byte) (s >>> 8);
byte b2 = (byte) (s & 0xFF);
return byte2Hex(b1) + byte2Hex(b2);
}
public static int byteToInt(byte b) {
return (int) b & 0xFF;
}
public static int byteToInt(byte first, byte second) {
int value = (first & 0xFF) << 8;
value += second & 0xFF;
return value;
}
public static short byte2Short(byte b1, byte b2) {
return (short) ((b1 << 8) | (b2 & 0xFF));
}
public static String getFormattedNanoTime(long nano) {
StringBuilder buf = new StringBuilder();
buf.append((int) (nano / 1000000));
buf.append("ms ");
buf.append(nano % 1000000);
buf.append("ns");
return buf.toString();
}
public static String formatDate(Date date)
{
return DateUtils.formatDateTime(null, date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR);
// return DateFormat.getDateInstance(DateFormat.SHORT).format(date);
}
public static String formatDateTime(Date date)
{
return formatDate(date)+" "+formatTime(date);
}
public static String formatTime(Date date)
{
return new SimpleDateFormat("HH:mm:ss").format(date);
// DateFormat.getTimeInstance(DateFormat.MEDIUM).format(date)
// return DateUtils.formatDateTime(null, date.getTime(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR);//DateFormat.getTimeInstance(DateFormat.MEDIUM).format(date);
}
public static byte[] getCurrentDateAsNumericEncodedByteArray(){
SimpleDateFormat format = new SimpleDateFormat("yyMMdd", Locale.US);
return fromHexString(format.format(new Date()));
}
//This prints all non-control characters common to all parts of ISO/IEC 8859
//See EMV book 4 Annex B: Table 36: Common Character Set
public static String getSafePrintChars(byte[] byteArray) {
if (byteArray == null) {
return "";
// throw new IllegalArgumentException("Argument 'byteArray' cannot be null");
}
return getSafePrintChars(byteArray, 0, byteArray.length);
}
public static String getSafePrintChars(byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
return "";
// throw new IllegalArgumentException("Argument 'byteArray' cannot be null");
}
if(byteArray.length < startPos+length){
throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+byteArray.length+")");
}
StringBuilder buf = new StringBuilder();
for (int i = startPos; i < startPos+length; i++) {
if (byteArray[i] >= (byte) 0x20 && byteArray[i] < (byte) 0x7F) {
buf.append((char) byteArray[i]);
} else {
buf.append(".");
}
}
return buf.toString();
}
public static byte[] hexToBytes(String str) {
byte[] bytes = new byte[str.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(str.substring(2 * i, 2 * i + 2),
16);
}
return bytes;
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
if( bytes==null ) return "[EMPTY]";
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
/**
* Converts a byte array into a hex string.
* @param byteArray the byte array source
* @return a hex string representing the byte array
*/
public static String byteArrayToHexString(final byte[] byteArray) {
if (byteArray == null) {
return "";
}
return byteArrayToHexString(byteArray, 0, byteArray.length);
}
public static String byteArrayToHexString(final byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
return "";
}
if(byteArray.length < startPos+length){
throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+byteArray.length+")");
}
// int readBytes = byteArray.length;
StringBuilder hexData = new StringBuilder();
int onebyte;
for (int i = 0; i < length; i++) {
onebyte = ((0x000000ff & byteArray[startPos+i]) | 0xffffff00);
hexData.append(Integer.toHexString(onebyte).substring(6));
}
return hexData.toString();
}
public static String int2Hex(int i) {
String hex = Integer.toHexString(i);
if (hex.length() % 2 != 0) {
hex = "0" + hex;
}
return hex;
}
public static String int2HexZeroPad(int i) {
String hex = int2Hex(i);
if (hex.length() % 2 != 0) {
hex = "0" + hex;
}
return hex;
}
/**
* The length of the returned array depends on the size of the int
* @param value
* @return
*/
public static byte[] intToByteArray(int value) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte one = (byte) (value >>> 24);
byte two = (byte) (value >>> 16);
byte three = (byte) (value >>> 8);
byte four = (byte) (value);
boolean found = false;
if (one > 0x00) {
baos.write(one);
found = true;
}
if (found || two > 0x00) {
baos.write(two);
found = true;
}
if (found || three > 0x00) {
baos.write(three);
}
baos.write(four);
return baos.toByteArray();
}
/**
* Returns a byte array with length = 2
* @param value
* @return
*/
public static byte[] intToByteArray2(int value) {
return new byte[]{
(byte) (value >>> 8),
(byte) value};
}
/**
* Returns a byte array with length = 4
* @param value
* @return
*/
public static byte[] intToByteArray4(int value) {
return new byte[]{
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public static byte[] longToByteArray8(long value) {
return new byte[]{
(byte) (value >>> 56),
(byte) (value >>> 48),
(byte) (value >>> 40),
(byte) (value >>> 32),
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public static int byteArrayToInt(byte[] byteArray) {
if( byteArray.length==1 ) return byteArray[0]&0xFF;
java.nio.ByteBuffer BB=java.nio.ByteBuffer.wrap(byteArray);
switch (byteArray.length)
{
case 2: return BB.getShort();
case 4: return BB.getInt();
default: throw new IllegalArgumentException("Length must be 1,2 or 4. Length = " + byteArray.length);
}
}
public static long byteArrayToLong(byte[] byteArray) {
if( byteArray.length==1 ) return byteArray[0]&0xFF;
java.nio.ByteBuffer BB=java.nio.ByteBuffer.wrap(byteArray);
switch (byteArray.length)
{
case 2: return BB.getShort();
case 4: return BB.getInt();
case 8: return BB.getLong();
default: throw new IllegalArgumentException("Length must be 1,2,4 or 8. Length = " + byteArray.length);
}
}
public static byte[] longToByteArray(long value)
{
return new byte[]{
(byte) (value >>> 56),
(byte) (value >>> 48),
(byte) (value >>> 40),
(byte) (value >>> 32),
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public static int byteArrayToInt(byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
throw new IllegalArgumentException("Parameter 'byteArray' cannot be null");
}
if (length <= 0 || length > 4) {
throw new IllegalArgumentException("Length must be between 1 and 4. Length = " + length);
}
if (length == 4 && Util.isBitSet(byteArray[startPos], 8)){
throw new IllegalArgumentException("Signed bit is set (leftmost bit): " + Util.byte2Hex(byteArray[startPos]));
}
int value = 0;
for (int i = 0; i < length; i++) {
value += ((byteArray[startPos+i] & 0xFF) << 8 * (length - i - 1));
}
return value;
}
public static long byteArrayToLong(byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
throw new IllegalArgumentException("Parameter 'byteArray' cannot be null");
}
if (length <= 0 || length > 8) {
throw new IllegalArgumentException("Length must be between 1 and 4. Length = " + length);
}
if (length == 8 && Util.isBitSet(byteArray[startPos], 8)){
throw new IllegalArgumentException("Signed bit is set (leftmost bit): " + Util.byte2Hex(byteArray[startPos]));
}
long value = 0;
for (int i = 0; i < length; i++) {
value += ((byteArray[startPos+i] & (long)0xFF) << 8 * (length - i - 1));
}
return value;
}
public static byte[] fromHexString(String encoded) {
encoded = removeSpaces(encoded);
if (encoded.length() == 0){
return new byte[0];
}
if ((encoded.length() % 2) != 0) {
throw new IllegalArgumentException("Input string must contain an even number of characters: "+encoded);
}
final byte result[] = new byte[encoded.length() / 2];
final char enc[] = encoded.toCharArray();
for (int i = 0; i < enc.length; i += 2) {
StringBuilder curr = new StringBuilder(2);
curr.append(enc[i]).append(enc[i + 1]);
result[i / 2] = (byte) Integer.parseInt(curr.toString(), 16);
}
return result;
}
public static String removeCRLFTab(String s) {
StringTokenizer st = new StringTokenizer(s, "\r\n\t", false);
StringBuilder buf = new StringBuilder();
while (st.hasMoreElements()) {
buf.append(st.nextElement());
}
return buf.toString();
}
public static String removeSpaces(String s) {
return s.replaceAll(" ", "");
}
public static String readInputStreamToString(InputStream is, String encoding) throws IOException {
InputStreamReader input = new InputStreamReader(is, encoding);
final int CHARS_PER_PAGE = 5000; //counting spaces
final char[] buffer = new char[CHARS_PER_PAGE];
StringBuilder output = new StringBuilder(CHARS_PER_PAGE);
for (int read = input.read(buffer, 0, buffer.length);
read != -1;
read = input.read(buffer, 0, buffer.length)) {
output.append(buffer, 0, read);
}
String text = output.toString();
return text;
}
public static void writeStringToFile(String string, String fileName, boolean append) throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter(fileName, append));
out.write(string);
out.close();
}
/**
* Binary Coded Decimal (BCD)
* @param val
* @return
*/
public static byte[] intToBinaryEncodedDecimalByteArray(int val){
String str = String.valueOf(val);
if(str.length() % 2 != 0){
str = "0"+str;
}
return Util.fromHexString(str);
}
/**
* This method converts the literal hex representation of a byte to an int.
* eg 0x70 = 70 (int)
* @param b
*/
public static int binaryCodedDecimalToInt(byte b) {
String hex = Util.byte2Hex(b);
try {
return Integer.parseInt(hex);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("The hex representation of argument b must be digits", ex);
}
}
/**
* This method converts the literal hex representation of a decimal
* encoded in 1-5 bytes to an int.
* The value should not be larger than Integer.MAX_VALUE
*
* eg 0x70 = 70 (decimal)
* eg 0x21 47 48 36 47 = 2147483647 (decimal)
* @param hex
*/
public static int binaryHexCodedDecimalToInt(String hex) {
if (hex == null) {
throw new IllegalArgumentException("Param hex cannot be null");
}
hex = Util.removeSpaces(hex);
if (hex.length() > 10) {
throw new IllegalArgumentException("There must be a maximum of 5 hex octets. hex=" + hex);
}
try {
return Integer.parseInt(hex);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Argument hex must be all digits. hex="+hex, ex);
}
}
/**
* This method converts a 1-5 byte BCD to an int.
* eg 0x7099 = 7099 (int)
* @param bcdArray
*/
public static int binaryHexCodedDecimalToInt(byte[] bcdArray) {
if (bcdArray == null) {
throw new IllegalArgumentException("Param bcdArray cannot be null");
}
return binaryHexCodedDecimalToInt(Util.byteArrayToHexString(bcdArray));
}
/**
* This returns a String with length = 8
* @param val
* @return
*/
public static String byte2BinaryLiteral(byte val) {
String s = Integer.toBinaryString(Util.byteToInt(val));
if (s.length() < 8) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8 - s.length(); i++) {
sb.append('0');
}
sb.append(s);
s = sb.toString();
}
return s;
}
/**
* Returns a bitset containing the values in bytes.
* The byte-ordering of bytes must be big-endian which means the most significant bit is in element 0.
*
* @param bytes
* @return
*/
public static BitSet byteArray2BitSet(byte[] bytes) {
BitSet bits = new BitSet();
for (int i = 0; i < bytes.length * 8; i++) {
if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
bits.set(i);
}
}
return bits;
}
/* Returns a byte array of at least length 1.
* The most significant bit in the result is guaranteed not to be a 1
* (since BitSet does not support sign extension).
* The byte-ordering of the result is big-endian which means the most significant bit is in element 0.
* The bit at index 0 of the bit set is assumed to be the least significant bit.
*/
public static byte[] bitSet2ByteArray(BitSet bits) {
byte[] bytes = new byte[bits.length() / 8 + 1];
for (int i = 0; i < bits.length(); i++) {
if (bits.get(i)) {
bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
}
}
return bytes;
}
/**
*
* @param val
* @param bitPos The leftmost bit is 8 (the most significant bit)
* @return
*/
public static boolean isBitSet(byte val, int bitPos) {
if (bitPos < 1 || bitPos > 8) {
throw new IllegalArgumentException("parameter 'bitPos' must be between 1 and 8. bitPos=" + bitPos);
}
if ((val >>> (bitPos - 1) & 0x1) == 1) {
return true;
}
return false;
}
// /**
// *
// * @param val
// * @return
// */
// public static int getBitsSetCount(byte val) {
// int numBitsSet = 0;
// for(int i=1; i<=8; i++){
// if(Util.isBitSet(val, i)){
// numBitsSet++;
// }
// }
// return numBitsSet;
// }
/**
*
* @param data
* @param bitPos The leftmost bit is 8
* @param on
* @return
*/
public static byte setBit(byte data, int bitPos, boolean on) {
if (bitPos < 1 || bitPos > 8) {
throw new IllegalArgumentException("parameter 'bitPos' must be between 1 and 8. bitPos=" + bitPos);
}
if (on) {
// set bit
return data |= 1 << (bitPos - 1);
} else {
// clear bit
return data &= ~(1 << (bitPos - 1));
}
}
public static byte[] generateRandomBytes(int numBytes) {
// TODO: get bytes from a hardware RNG, or set seed
byte[] rndBytes = new byte[numBytes];
SecureRandom random = new SecureRandom();
random.nextBytes(rndBytes);
return rndBytes;
}
public static byte generateRandomByte() {
SecureRandom random = new SecureRandom();
return (byte)(random.nextInt()&0xFF);
}
public static InputStream loadResource(Class<?> cls, String path){
return cls.getResourceAsStream(path);
}
/**
* Copies the specified array, prepending 0x00, or cutting off MSBytes if necessary
* @param original
* @param newLength
* @return
*/
public static byte[] resizeArray(byte[] original, int newLength) {
if(original == null){
throw new IllegalArgumentException("byte array cannot be null");
}
if(newLength < 0){
throw new IllegalArgumentException("Illegal new length: "+newLength+". Must be >= 0");
}
if(newLength == 0){
return new byte[0];
}
byte[] tmp = new byte[newLength];
int srcPos = tmp.length > original.length ? 0 : original.length - tmp.length;
int destPos = tmp.length > original.length ? tmp.length - original.length : 0;
int length = tmp.length > original.length ? original.length : tmp.length;
System.arraycopy(original, srcPos, tmp, destPos, length);
return tmp;
}
public static byte[] copyByteArray(byte[] array2Copy){
// byte[] copy = new byte[array2Copy.length];
// System.arraycopy(array2Copy, 0, copy, 0, array2Copy.length);
// return copy;
if (array2Copy == null) {
//return new byte[0] instead?
throw new IllegalArgumentException("Argument 'array2Copy' cannot be null");
}
return copyByteArray(array2Copy, 0, array2Copy.length);
}
public static byte[] copyByteArray(byte[] array2Copy, int startPos, int length){
if (array2Copy == null) {
//return new byte[0] instead?
throw new IllegalArgumentException("Argument 'array2Copy' cannot be null");
}
if(array2Copy.length < startPos+length){
throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+array2Copy.length+")");
}
byte[] copy = new byte[array2Copy.length];
System.arraycopy(array2Copy, startPos, copy, 0, length);
return copy;
}
public static String getStackTrace(Throwable t){
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
public static Class<?> getCallerClass(int i) {
Class<?>[] classContext = new SecurityManager() {
@Override public Class<?>[] getClassContext() {
return super.getClassContext();
}
}.getClassContext();
if (classContext != null) {
for (int j = 0; j < classContext.length; j++) {
if (classContext[j] == Util.class) {
return classContext[i+j];
}
}
} else {
// SecurityManager.getClassContext() returns null on Android 4.0
try {
StackTraceElement[] classNames = Thread.currentThread().getStackTrace();
for (int j = 0; j < classNames.length; j++) {
if (Class.forName(classNames[j].getClassName()) == Util.class) {
return Class.forName(classNames[i+j].getClassName());
}
}
} catch (ClassNotFoundException e) { }
}
return null;
}
public static String decodeOID(byte[] enc){
StringBuilder sb = new StringBuilder();
//First OID Component (standard)
//0: ITU-T
//1: ISO
//2: joint-iso-itu-t
//Second OID Component (part in a multi part standard)
//0: standard
//1: registration-authority
//2: member-body
//3: identified-organization
long firstSubidentifier = 0;
int i=0;
while(Util.isBitSet(enc[i], 8)){
firstSubidentifier = (firstSubidentifier << 7) | (enc[i] & 0x7f);
i++;
}
firstSubidentifier = (firstSubidentifier << 7) | (enc[i] & 0x7f);
i++;
if(firstSubidentifier >= 80){
long firstOIDComp = 2;
long secondOIDComp = firstSubidentifier - 80;
sb.append(firstOIDComp).append(".").append(secondOIDComp);
}else{
long secondOIDComp = firstSubidentifier % 40;
long firstOIDComp = (firstSubidentifier - secondOIDComp)/40;
sb.append(firstOIDComp).append(".").append(secondOIDComp);
}
for(; i<enc.length; i++){
sb.append(".");
long subIdentifier = 0;
while(Util.isBitSet(enc[i], 8)){
subIdentifier = (subIdentifier << 7) | (enc[i] & 0x7f);
i++;
}
subIdentifier = (subIdentifier << 7) | (enc[i] & 0x7f);
sb.append(subIdentifier);
}
String oid = sb.toString();
String desc = getOIDDescription(oid);
return oid + ((desc!=null && !desc.isEmpty())?" ("+desc +")":"");
}
//Simple OID registry
//See: http://www.oid-info.com/
public static String getOIDDescription(String oid){
// 1.2.840 - one of 2 US country OIDs
// 1.2.840.114283 - Global Platform
// 1.3.6.1 - the Internet OID
// 1.3.6.1.4.1 - IANA-assigned company OIDs, used for private MIBs and such things
// 1.3.6.1.4.1.42 - Sun Microsystems
// 1.3.6.1.4.1.42.2 - Sun Products
// 1.3.6.1.4.1.42.2.110 - java[XML]software
// 1.3.6.1.4.1.42.2.110.1.2 - (Unknown - Java Card?)
if(oid.startsWith("1.2.840.114283.1")){
return "Global Platform - Card Recognition Data";
}
if(oid.startsWith("1.2.840.114283.2")){
return "Global Platform v"+oid.substring(17);
}
if(oid.startsWith("1.2.840.114283.3")){
return "Global Platform - Card Identification Scheme";
}
if(oid.startsWith("1.2.840.114283.4")){
return "Global Platform SCP "+oid.substring(17, 18) + " implementation option 0x"+Util.int2Hex(Integer.parseInt(oid.substring(19)));
}
if(oid.startsWith("1.2.840.114283")){
return "Global Platform";
}
if(oid.startsWith("1.2.840")){
return "USA";
}
if(oid.startsWith("1.3.6.1.4.1.42.2.110.1.2")){
return "Sun Microsystems - Java Card ?";
}
if(oid.startsWith("1.3.6.1.4.1.42.2")){
return "Sun Microsystems - Products";
}
// if(oid.startsWith("1.3.656.840."))
//JCOP includes GP refinements according to Visa GP 2.1.1 specification.
//This tag is populated accordingly (Visa specific).
//The last number tells you what configuration it is (3: SSD + PKI, 2: PKI, 1: just symmetric crypto).
//Unfortunately this standard is not open.
return "";
}
public static void main(String[] args) {
// System.out.println(Util.isBitSet((byte) 0x5f, 2)); // 0101 1111
// System.out.println(Util.isBitSet((byte) 0x9f, 2)); // 1001 1111
//
// System.out.println(Util.byte2Short((byte) 0x6F, (byte) 0xEF));
// System.out.println(Util.short2Hex(Util.byte2Short((byte) 0x6F, (byte) 0xEF)));
//
// System.out.println(Util.byteArrayToInt(new byte[]{(byte) 0x6F, (byte) 0xEF}));
// System.out.println(Util.byteArrayToHexString(Util.intToByteArray(28655)));
//
// System.out.println(Util.byte2BinaryLiteral((byte) 0x00));
// System.out.println(Util.byte2BinaryLiteral((byte) 0x3F));
// System.out.println(Util.byte2BinaryLiteral((byte) 0x80));
// System.out.println(Util.byte2BinaryLiteral((byte) 0xAA));
// System.out.println(Util.byte2BinaryLiteral((byte) 0xFF));
//
// System.out.println(Util.byte2BinaryLiteral((byte) 0x8A));
// System.out.println(Util.byte2BinaryLiteral(Util.setBit((byte) 0x8A, 5, true)));
// System.out.println(Util.byte2BinaryLiteral(Util.setBit((byte) 0x8A, 8, false)));
//
// System.out.println(Util.byteArrayToLong(Util.fromHexString("7f ff ff ff ff ff ff ff"), 0, 8));
// System.out.println(Util.byteArrayToLong(Util.fromHexString("22 18 09 04 0b 00 e0 30 23 07 00 00 00 42 d2 85 4e 23 07 00 00 00 00 21 69 42"), 13, 4));
System.out.println("1.2.840.114283.1 : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 01")));
System.out.println("1.2.840.114283.2.2.1.1 : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 02 02 01 01")));
System.out.println("1.2.840.114283.4.XXXX : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 04 02 15"))); //JCOP 31
System.out.println("1.2.840.114283.4.XXXX : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 04 01 05"))); //JCOP 31
System.out.println("Sun Microsystems : " + decodeOID(Util.fromHexString("2b 06 01 04 01 2a 02 6e 01 02")));
System.out.println("Unknown : " + decodeOID(Util.fromHexString("2b 85 10 86 48 64 02 01 03")));
System.out.println("{2 100 3} : " + decodeOID(Util.fromHexString("813403")));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01}, 0)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01}, 1)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01}, 2)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01, 0x02}, 1)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01, 0x02}, 4)));
}
public static byte[] calculateCRC16(byte[] bytes) {
byte chBlock;
// STEP 1 Initialize the CRC-16 value
int wCRC = 0x6363; // ITU-V.41
int i = 0;
// STEP 2 Update data and Calucuate their CRC
do {
chBlock = bytes[i++];
chBlock ^= (byte) (wCRC & 0x00FF);
chBlock = (byte) (chBlock ^ (chBlock << 4));
wCRC = ((wCRC >> 8) ^ ((chBlock & 0xFF) << 8) & 0xFFFF) ^ (((chBlock & 0xFF) << 3) & 0xFFFF) ^ (((chBlock & 0xFF) >> 4) & 0xFFFF);// (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4);
} while (i < bytes.length);
return new byte[]{(byte) (wCRC & 0xFF), (byte) ((wCRC & 0xFFFF) >> 8)};
}
public static String formatDateTimeToFileName(Date date) {
return new SimpleDateFormat("yyyy_MM_dd__HH_mm_ss", Locale.US).format(date);
}
}

View file

@ -0,0 +1,488 @@
package com.tangem.domain.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
import android.util.Log;
import com.tangem.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
public final class BTCUtils {
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<TangemCard.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
for(TangemCard.UnspentTransaction current: rawTxList)
{
byte[] rawTxByte = BTCUtils.fromHex(current.Raw);
if (rawTxByte == null)
{
continue;
}
Transaction baseTx = new Transaction(rawTxByte);
if(baseTx.inputs.length == 0 || baseTx.outputs.length == 0)
throw new IllegalArgumentException("Unable to decode given transaction");
byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte));
String txHashForBuild = current.txID;
byte[] sign = null;
for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) {
Transaction.Output output = baseTx.outputs[outputIndex];
// find outputs
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign));
}
}
}
return unspentOutputs;
}
public static byte[] fromHex(String s) {
if (s != null) {
try {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (!Character.isWhitespace(ch)) {
sb.append(ch);
}
}
s = sb.toString();
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int hi = (Character.digit(s.charAt(i), 16) << 4);
int low = Character.digit(s.charAt(i + 1), 16);
if (hi >= 256 || low < 0 || low >= 16) {
return null;
}
data[i / 2] = (byte) (hi | low);
}
return data;
} catch (Exception ignored) {
}
}
return null;
}
public static byte[] reverse(byte[] bytes) {
byte[] result = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
result[i] = bytes[bytes.length - i - 1];
}
return result;
}
public static byte[] reverseInPlace(byte[] bytes) {
int len = bytes.length / 2;
for (int i = 0; i < len; i++) {
byte t = bytes[i];
bytes[i] = bytes[bytes.length - i - 1];
bytes[bytes.length - i - 1] = t;
}
return bytes;
}
public static int findSpendableOutput(Transaction tx, String forAddress, long minAmount) throws BitcoinException {
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(forAddress).bytes;
int indexOfOutputToSpend = -1;
for (int indexOfOutput = 0; indexOfOutput < tx.outputs.length; indexOfOutput++) {
Transaction.Output output = tx.outputs[indexOfOutput];
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
indexOfOutputToSpend = indexOfOutput;
break;//only one input is supported for now
}
}
if (indexOfOutputToSpend == -1) {
throw new BitcoinException(BitcoinException.ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS, "No spendable standard outputs for " + forAddress + " have found", forAddress);
}
final long spendableOutputValue = tx.outputs[indexOfOutputToSpend].value;
if (spendableOutputValue < minAmount) {
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Unspent amount is too small: " + spendableOutputValue, spendableOutputValue);
}
return indexOfOutputToSpend;
}
public static void verify(Transaction.Script[] scripts, Transaction spendTx) throws Transaction.Script.ScriptInvalidException {
for (int i = 0; i < scripts.length; i++) {
Stack<byte[]> stack = new Stack<>();
spendTx.inputs[i].script.run(stack);//load signature+public key
scripts[i].run(i, spendTx, stack); //verify that this transaction able to spend that output
if (Transaction.Script.verifyFails(stack)) {
throw new Transaction.Script.ScriptInvalidException("Signature is invalid");
}
}
}
public static class FeeChangeAndSelectedOutputs {
public final long amountForRecipient, change, fee;
public final ArrayList<UnspentOutputInfo> outputsToSpend;
public FeeChangeAndSelectedOutputs(long fee, long change, long amountForRecipient, ArrayList<UnspentOutputInfo> outputsToSpend) {
this.fee = fee;
this.change = change;
this.amountForRecipient = amountForRecipient;
this.outputsToSpend = outputsToSpend;
}
}
public static FeeChangeAndSelectedOutputs calcFeeChangeAndSelectOutputsToSpend(List<UnspentOutputInfo> unspentOutputs, long amountToSend, long extraFee, final boolean isPublicKeyCompressed) throws BitcoinException {
long fee = 0;//calculated below
long change = 0;
long valueOfUnspentOutputs;
ArrayList<UnspentOutputInfo> outputsToSpend = new ArrayList<>();
if (amountToSend <= 0) {
//transfer all funds from these addresses to outputAddress
change = 0;
valueOfUnspentOutputs = 0;
for (UnspentOutputInfo outputInfo : unspentOutputs) {
outputsToSpend.add(outputInfo);
valueOfUnspentOutputs += outputInfo.value;
}
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, 1, isPublicKeyCompressed);
fee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, valueOfUnspentOutputs - MIN_FEE_PER_KB * (1 + txLen / 1000));
amountToSend = valueOfUnspentOutputs - fee - extraFee;
} else {
valueOfUnspentOutputs = 0;
for (UnspentOutputInfo outputInfo : unspentOutputs) {
outputsToSpend.add(outputInfo);
valueOfUnspentOutputs += outputInfo.value;
long updatedFee = MIN_FEE_PER_KB;
for (int i = 0; i < 3; i++) {
fee = updatedFee;
change = valueOfUnspentOutputs - fee - extraFee - amountToSend;
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, change > 0 ? 2 : 1, isPublicKeyCompressed);
updatedFee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, change > 0 ? Math.min(amountToSend, change) : amountToSend);
if (updatedFee == fee) {
break;
}
}
fee = updatedFee;
if (valueOfUnspentOutputs >= amountToSend + fee + extraFee) {
break;
}
}
}
if (amountToSend > valueOfUnspentOutputs - fee) {
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Not enough funds", valueOfUnspentOutputs - fee);
}
if (outputsToSpend.isEmpty()) {
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No outputs to spend");
}
if (fee + extraFee > MAX_ALLOWED_FEE) {
throw new BitcoinException(BitcoinException.ERR_FEE_IS_TOO_BIG, "Fee is too big", fee);
}
if (fee < 0 || extraFee < 0) {
throw new BitcoinException(BitcoinException.ERR_FEE_IS_LESS_THEN_ZERO, "Incorrect fee", fee);
}
if (change < 0) {
throw new BitcoinException(BitcoinException.ERR_CHANGE_IS_LESS_THEN_ZERO, "Incorrect change", change);
}
if (amountToSend < 0) {
throw new BitcoinException(BitcoinException.ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO, "Incorrect amount to send", amountToSend);
}
return new FeeChangeAndSelectedOutputs(fee + extraFee, change, amountToSend, outputsToSpend);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,389 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.util.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.domain.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class BtcCashEngine extends CoinEngine {
public String GetNextNode(TangemCard mCard) {
return "35.157.238.5";
}
public int GetNextNodePort(TangemCard mCard) {
return 51001;
}
public String GetNode(TangemCard mCard) {
return "35.157.238.5";
}
public int GetNodePort(TangemCard mCard) {
return 51001;
}
public void SwitchNode(TangemCard mCard) {
}
public boolean InOutPutVisible() {
return true;
}
public boolean AwaitingConfirmation(TangemCard card) {
return card.getBalanceUnconfirmed() != 0;
}
public String GetBalanceWithAlter(TangemCard mCard) {
return GetBalance(mCard);
}
public boolean IsBalanceAlterNotZero(TangemCard card) {
return true;
}
public Long GetBalanceLong(TangemCard mCard) {
return mCard.getBalance();
}
public boolean IsBalanceNotZero(TangemCard card) {
return card.getBalance() > 0;
}
public boolean CheckAmount(TangemCard 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(TangemCard card) {
return card.hasBalanceInfo();
}
public String GetBalanceCurrency(TangemCard card) {
return "mBCH";
}
public boolean CheckUnspentTransaction(TangemCard mCard) {
return mCard.getUnspentTransactions().size() != 0;
}
public String GetFeeCurrency() {
return "mBCH";
}
public boolean ValdateAddress(String address, TangemCard 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(TangemCard card) {
return 0;
}
public String GetContractAddress(TangemCard card) {
return "";
}
public boolean IsNeedCheckNode() {
return true;
}
public Uri getShareWalletURIExplorer(TangemCard mCard) {
return Uri.parse((mCard.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + mCard.getWallet());
}
public Uri getShareWalletURI(TangemCard mCard) {
return Uri.parse("bitcoincash:" + mCard.getWallet());
}
public boolean CheckAmountValie(TangemCard 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(TangemCard mCard, String fee) {
return GetAmountEqualentDescriptor(mCard, fee);
}
@Override
public String GetBalanceEquivalent(TangemCard 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(TangemCard mCard) {
if (mCard.hasBalanceInfo()) {
Double balance = mCard.AmountFromInternalUnits(mCard.getBalance());
return mCard.getAmountDescription(balance);
} else {
return "-- -- -- " + mCard.getBlockchain().getCurrency();
}
}
public String GetBalanceValue(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard mCard, String value) {
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value) / 1000.0, mCard.getRate());
}
public byte[] Sign(String feeValue, String amountValue, String toValue, TangemCard 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<TangemCard.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() == TangemCard.SigningMethod.Sign_Raw || mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
dataForSign[i] = newTX;
} else {
dataForSign[i] = doubleHashData;
}
}
byte[] signFromCard = null;
if (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || mCard.getSigningMethod() == TangemCard.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() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
byte[] encodingSign = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX;
}
}

View file

@ -0,0 +1,524 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.util.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.domain.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class BtcEngine extends CoinEngine {
public String GetNextNode(TangemCard mCard) {
return getNextServiceHost(mCard);
}
public int GetNextNodePort(TangemCard mCard) {
return getNextServicePort(mCard);
}
public String GetNode(TangemCard mCard) {
return getServiceHost(mCard);
}
public int GetNodePort(TangemCard mCard) {
return getServicePort(mCard);
}
public void SwitchNode(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard mCard) {
switch (mCard.getBlockchain()) {
case Bitcoin: {
setNextDynamicIndex();
return GetBitcoinServicePorts()[dynamicIndex];//8080;
}
case BitcoinTestNet: {
setNextDynamicTestNet();
return GetBitcoinTestNetServicePorts()[dynamicTestNetIndex];//51001;
}
}
return 8080;
}
public static int getServicePort(TangemCard 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(TangemCard card) {
return card.getBalanceUnconfirmed() != 0;
}
public String GetBalanceWithAlter(TangemCard mCard) {
return GetBalance(mCard);
}
public boolean IsBalanceAlterNotZero(TangemCard card) {
return true;
}
public Long GetBalanceLong(TangemCard mCard) {
return mCard.getBalance();
}
public boolean IsBalanceNotZero(TangemCard card) {
return card.getBalance() > 0;
}
public boolean CheckAmount(TangemCard 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(TangemCard card) {
return card.hasBalanceInfo();
}
public String GetBalanceCurrency(TangemCard card) {
return "mBTC";
}
public boolean CheckUnspentTransaction(TangemCard mCard) {
return mCard.getUnspentTransactions().size() != 0;
}
public String GetFeeCurrency() {
return "mBTC";
}
public boolean ValdateAddress(String address, TangemCard 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(TangemCard card) {
return 0;
}
public String GetContractAddress(TangemCard card) {
return "";
}
public boolean IsNeedCheckNode() {
return true;
}
public Uri getShareWalletURIExplorer(TangemCard mCard) {
return Uri.parse((mCard.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + mCard.getWallet());
}
public Uri getShareWalletURI(TangemCard mCard) {
return Uri.parse("bitcoin:" + mCard.getWallet());
}
public boolean CheckAmountValie(TangemCard 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(TangemCard mCard, String fee) {
return GetAmountEqualentDescriptor(mCard, fee);
}
@Override
public String GetBalanceEquivalent(TangemCard 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(TangemCard mCard) {
if (mCard.hasBalanceInfo()) {
Double balance = mCard.AmountFromInternalUnits(mCard.getBalance());
return mCard.getAmountDescription(balance);
} else {
return "-- -- -- " + mCard.getBlockchain().getCurrency();
}
}
public String GetBalanceValue(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard mCard, String value) {
return getAmountEquivalentDescriptionBTC(Double.parseDouble(value) / 1000.0, mCard.getRate());
}
public byte[] Sign(String feeValue, String amountValue, String toValue, TangemCard 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<TangemCard.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() == TangemCard.SigningMethod.Sign_Raw || mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
dataForSign[i] = newTX;
} else {
dataForSign[i] = doubleHashData;
}
}
byte[] signFromCard = null;
if (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || mCard.getSigningMethod() == TangemCard.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() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey);
unspentOutputs.get(i).scriptForBuild = encodingSign;
}
byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change);
return realTX;
}
}

View file

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

View file

@ -0,0 +1,82 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import com.tangem.domain.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(TangemCard mCard);
public abstract int GetNextNodePort(TangemCard mCard);
public abstract String GetNode(TangemCard mCard);
public abstract int GetNodePort(TangemCard mCard);
public abstract void SwitchNode(TangemCard mCard);
public abstract boolean AwaitingConfirmation(TangemCard card);
public abstract boolean HasBalanceInfo(TangemCard card);
public abstract boolean IsBalanceNotZero(TangemCard card);
public abstract boolean IsBalanceAlterNotZero(TangemCard card);
public abstract boolean CheckAmount(TangemCard card, String amount) throws Exception;
public abstract int GetTokenDecimals(TangemCard card);
public abstract String GetContractAddress(TangemCard card);
public abstract byte[] Sign(String feeValue, String amountValue, String toValue, TangemCard mCard, CardProtocol protocol) throws Exception;
public abstract boolean CheckUnspentTransaction(TangemCard mCard);
public abstract Uri getShareWalletURIExplorer(TangemCard mCard);
public abstract Long GetBalanceLong(TangemCard mCard);
public abstract Uri getShareWalletURI(TangemCard mCard);
public abstract String EvaluteFeeEquivalent(TangemCard mCard, String fee);
public abstract boolean CheckAmountValie(TangemCard mCard, String amount, String fee, Long minFeeInInternalUnits);
public abstract boolean InOutPutVisible();
public abstract String GetBalance(TangemCard mCard);
public abstract String GetBalanceWithAlter(TangemCard mCard);
public abstract String GetBalanceCurrency(TangemCard card);
public abstract String GetFeeCurrency();
public abstract boolean IsNeedCheckNode();
public abstract String GetBalanceEquivalent(TangemCard mCard);
public abstract String GetBalanceValue(TangemCard mCard);
public abstract String GetAmountDescription(TangemCard mCard, String amount) throws Exception;
public abstract String GetAmountEqualentDescriptor(TangemCard mCard, String value);
public abstract boolean ValdateAddress(String address, TangemCard catd);
public abstract String calculateAddress(TangemCard mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException;
public abstract String ConvertByteArrayToAmount(TangemCard mCard, byte[] bytes) throws Exception;
public abstract byte[] ConvertAmountToByteArray(TangemCard mCard, String amount) throws Exception;
}

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,392 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.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.domain.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 15.02.2018.
*/
public class EthEngine extends CoinEngine {
public String GetNextNode(TangemCard mCard)
{
return "abc1.hsmiths.com";
}
public int GetNextNodePort(TangemCard mCard)
{
return 60001;
}
public String GetNode(TangemCard mCard)
{
return "abc1.hsmiths.com";
}
public int GetNodePort(TangemCard mCard)
{
return 60001;
}
public void SwitchNode(TangemCard mCard)
{
}
public boolean InOutPutVisible()
{
return false;
}
public String GetBalanceCurrency(TangemCard card)
{
return "ETH";
}
public boolean AwaitingConfirmation(TangemCard 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(TangemCard card)
{
return 0;
}
public String GetContractAddress(TangemCard card)
{
return "";
}
public boolean ValdateAddress(String address, TangemCard 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(TangemCard 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(TangemCard mCard) {
String dec = mCard.getDecimalBalance();
BigDecimal d = convertToEth(dec);
return getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
@Override
public String GetBalance(TangemCard mCard) {
if(!HasBalanceInfo(mCard)){
return "-- -- -- " + GetBalanceCurrency(mCard);
}
String output = GetBalanceValue(mCard);
String s = output + " " + GetBalanceCurrency(mCard);
return s;
}
public Long GetBalanceLong(TangemCard mCard)
{
return mCard.getBalance();
}
public String GetBalanceWithAlter(TangemCard mCard)
{
return GetBalance(mCard);
}
public boolean IsBalanceAlterNotZero(TangemCard card)
{
return true;
}
public boolean IsBalanceNotZero(TangemCard 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(TangemCard mCard, byte[] bytes) throws Exception {
throw new Exception("Not implemented");
}
@Override
public byte[] ConvertAmountToByteArray(TangemCard mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
@Override
public String GetAmountDescription(TangemCard mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
public String GetAmountEqualentDescriptor(TangemCard mCard, String value)
{
BigDecimal d = new BigDecimal(value);
return getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
public boolean CheckAmount(TangemCard 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(TangemCard card)
{
return card.hasBalanceInfo();
}
public Uri getShareWalletURI(TangemCard mCard)
{
return Uri.parse("" + mCard.getWallet());
}
public Uri getShareWalletURIExplorer(TangemCard 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(TangemCard mCard)
{
return true;
}
public boolean CheckAmountValie(TangemCard 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(TangemCard 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(TangemCard 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, TangemCard mCard, CardProtocol protocol) throws Exception {
BigInteger nonceValue = mCard.GetConfirmTXCount();
byte[] pbKey = mCard.getWalletPublicKey();
boolean flag = (mCard.getSigningMethod()== TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = mCard.getIssuer();
BigInteger fee = new BigInteger(feeValue, 10);
BigDecimal amountDec = new BigDecimal(amountValue);
amountDec = amountDec.multiply(new BigDecimal("1000000000"));
BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10);
amount = amount.subtract(fee);
BigInteger nonce = nonceValue;
BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000));
BigInteger gasLimit = BigInteger.valueOf(21000);
Integer chainId = mCard.getBlockchain() == Blockchain.Ethereum ? ETH_Transaction.ChainEnum.Mainnet.getValue() : ETH_Transaction.ChainEnum.Rinkeby.getValue();
Long multiplicator = 1000000000L;
amount = amount.multiply(BigInteger.valueOf(multiplicator));
gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator));
String to = toValue;
if (to.startsWith("0x") || to.startsWith("0X")) {
to = to.substring(2);
}
ETH_Transaction tx = ETH_Transaction.create(to, amount, nonce, gasPrice, gasLimit, chainId);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if(!f)
{
Log.e("ETH-CHECK", "Sign Failed.");
}
tx.signature = new ECDSASignature_ETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
byte[] realTX = tx.getEncoded();
return realTX;
}
}

View file

@ -0,0 +1,62 @@
package com.tangem.domain.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();
}
public interface FingerprintHelperListener {
public void authenticationFailed(String error);
public void authenticationSucceeded(FingerprintManager.AuthenticationResult result);
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
listener.authenticationFailed("Authentication error\n" + errString);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
listener.authenticationFailed("Authentication help\n" + helpString);
}
@Override
public void onAuthenticationFailed() {
listener.authenticationFailed("Authentication failed.");
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
listener.authenticationSucceeded(result);
}
}

View file

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

View file

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

View file

@ -0,0 +1,120 @@
package com.tangem.domain.wallet;
import com.tangem.domain.cardReader.CardCrypto;
import java.util.Arrays;
/**
* Created by dvol on 14.11.2017.
*/
public enum Issuer {
Unknown("Unknown", "Unknown", null, null, null, null),
SMART_CASH_AG("SMART CASH AG", "SMART CASH AG",
IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey),
IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)),
TANGEM_SDK("TANGEM SDK", "TANGEM SDK",
IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey),
IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)),
TANGEM("TANGEM", "TANGEM", null, IssuerKeyStorage.tangemPublicDataKey, null, IssuerKeyStorage.tangemPublicTransactionKey)
;
static class IssuerKeyStorage {
private static final byte[] sdkPrivateDataKey = new byte[]{
(byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18,
(byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57,
(byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08,
(byte) 0x39, (byte) 0x27, (byte) 0x37, (byte) 0x2D, (byte) 0x40, (byte) 0xDA, (byte) 0x9E, (byte) 0x92};
private static final byte[] sdkPrivateTransactionKey = new byte[]{
(byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18,
(byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57,
(byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08,
(byte) 0x19, (byte) 0x18, (byte) 0x17, (byte) 0x16, (byte) 0x15, (byte) 0x14, (byte) 0x13, (byte) 0x12};
private static byte[] tangemPublicDataKey = {
(byte) 0x04 ,
(byte) 0x81 ,(byte) 0x96 ,(byte) 0xAA ,(byte) 0x4B ,(byte) 0x41 ,(byte) 0x0A ,(byte) 0xC4 ,(byte) 0x4A,
(byte) 0x3B ,(byte) 0x9C ,(byte) 0xCE ,(byte) 0x18 ,(byte) 0xE7 ,(byte) 0xBE ,(byte) 0x22 ,(byte) 0x6A,
(byte) 0xEA ,(byte) 0x07 ,(byte) 0x0A ,(byte) 0xCC ,(byte) 0x83 ,(byte) 0xA9 ,(byte) 0xCF ,(byte) 0x67,
(byte) 0x54 ,(byte) 0x0F ,(byte) 0xAC ,(byte) 0x49 ,(byte) 0xAF ,(byte) 0x25 ,(byte) 0x12 ,(byte) 0x9F,
(byte) 0x6A ,(byte) 0x53 ,(byte) 0x8A ,(byte) 0x28 ,(byte) 0xAD ,(byte) 0x63 ,(byte) 0x41 ,(byte) 0x35,
(byte) 0x8E ,(byte) 0x3C ,(byte) 0x4F ,(byte) 0x99 ,(byte) 0x63 ,(byte) 0x06 ,(byte) 0x4F ,(byte) 0x7E,
(byte) 0x36 ,(byte) 0x53 ,(byte) 0x72 ,(byte) 0xA6 ,(byte) 0x51 ,(byte) 0xD3 ,(byte) 0x74 ,(byte) 0xE5,
(byte) 0xC2 ,(byte) 0x3C ,(byte) 0xDD ,(byte) 0x37 ,(byte) 0xFD ,(byte) 0x09 ,(byte) 0x9B ,(byte) 0xF2};
private static byte[] tangemPublicTransactionKey = {
(byte) 0x04 ,
(byte) 0x34 ,(byte) 0x3D ,(byte) 0x40 ,(byte) 0x49 ,(byte) 0x6C ,(byte) 0xBE ,(byte) 0x1F ,(byte) 0xE8,
(byte) 0xA8 ,(byte) 0xC0 ,(byte) 0x26 ,(byte) 0x57 ,(byte) 0x5C ,(byte) 0x43 ,(byte) 0x5A ,(byte) 0x29,
(byte) 0x14 ,(byte) 0x1E ,(byte) 0xA3 ,(byte) 0xBC ,(byte) 0x33 ,(byte) 0x5D ,(byte) 0xA5 ,(byte) 0x54,
(byte) 0x9A ,(byte) 0xB6 ,(byte) 0xC6 ,(byte) 0x46 ,(byte) 0x85 ,(byte) 0xA6 ,(byte) 0x46 ,(byte) 0x84,
(byte) 0x80 ,(byte) 0x36 ,(byte) 0xD4 ,(byte) 0x81 ,(byte) 0xCF ,(byte) 0x9A ,(byte) 0x98 ,(byte) 0x93,
(byte) 0x90 ,(byte) 0xA8 ,(byte) 0xB0 ,(byte) 0x34 ,(byte) 0xB2 ,(byte) 0x29 ,(byte) 0xD9 ,(byte) 0x9B,
(byte) 0xD4 ,(byte) 0x9E ,(byte) 0x6F ,(byte) 0x07 ,(byte) 0xD2 ,(byte) 0xFF ,(byte) 0x02 ,(byte) 0x74,
(byte) 0x6E ,(byte) 0xA2 ,(byte) 0x65 ,(byte) 0xEF ,(byte) 0x99 ,(byte) 0x38 ,(byte) 0x0A ,(byte) 0x80};
public static byte[] GeneratePublicKey(byte[] privateKey) {
try {
return CardCrypto.GeneratePublicKey(privateKey);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
}
private String ID;
private String officialName;
private byte[] privateDataKeyArray;
private byte[] publicDataKeyArray;
private byte[] privateTransactionKeyArray;
private byte[] publicTransactionKeyArray;
Issuer(String id, String officialName, byte[] privateDataKey, byte[] publicDataKey, byte[] privateTransactionKey, byte[] publicTransactionKey) {
this.ID = id;
this.officialName = officialName;
this.privateDataKeyArray = privateDataKey;
this.privateTransactionKeyArray = privateTransactionKey;
this.publicDataKeyArray = publicDataKey;
this.publicTransactionKeyArray = publicTransactionKey;
}
public byte[] getPublicDataKey() {
return publicDataKeyArray;
}
public byte[] getPublicTransactionKey() {
return publicTransactionKeyArray;
}
public byte[] getPrivateDataKey() {
return privateDataKeyArray;
}
public byte[] getPrivateTransactionKey() {
return privateTransactionKeyArray;
}
public byte[] getID() {
return ID.getBytes();
}
public String getOfficialName() {
return officialName;
}
public static Issuer FindIssuer(String ID, byte[] publicDataKey) {
Issuer[] issuers = Issuer.values();
for (int i = 1; i < issuers.length; i++) {
if (issuers[i].ID.equals(ID) && Arrays.equals(issuers[i].getPublicDataKey(), publicDataKey)) {
return issuers[i];
}
}
return Issuer.Unknown;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,205 @@
package com.tangem.domain.wallet;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import com.tangem.domain.cardReader.CardProtocol;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
/**
* Created by dvol on 12.09.2017.
* Global PIN Storage
*/
public class PINStorage {
private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2;
private static SharedPreferences sharedPreferences=null;
public static void Init(Context context) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
mSavedPIN = sharedPreferences.getString("SavedPIN", null);
mUserPIN = null;
mLastUsedPIN = null;
mEncryptedPIN = null;
mPIN2 = null;
}
public 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;
}
public static void setLastUsedPIN(String PIN) {
mLastUsedPIN = PIN;
}
public static void setUserPIN(String PIN) {
mUserPIN = PIN;
}
public static void setPIN2(String PIN) {
mPIN2 = PIN;
}
public 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();
}
}
public 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();
}
public 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();
}
}
public static byte[] loadEncryptedIV() {
String sIV = sharedPreferences.getString("EncryptedIV", "");
// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV));
return Base64.decode(sIV, Base64.NO_WRAP);
}
public 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;
}
public static boolean haveEncryptedPIN() {
return sharedPreferences.getString("EncryptedPIN", null) != null;
}
public 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();
}
public 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();
}
}
public static byte[] loadEncryptedIV2() {
String sIV = sharedPreferences.getString("EncryptedIV2", "");
// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV));
return Base64.decode(sIV, Base64.NO_WRAP);
}
public 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;
}
public static boolean haveEncryptedPIN2() {
return sharedPreferences.getString("EncryptedPIN2", null) != null;
}
public static void deleteEncryptedPIN2() {
mPIN2 = null;
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove("EncryptedPIN2");
editor.remove("EncryptedIV2");
editor.apply();
}
public static String getPIN2() {
return mPIN2;
}
public static boolean isDefaultPIN(String pin) {
return (pin != null) && (CardProtocol.DefaultPIN.equals(pin));
}
public static boolean isDefaultPIN2(String pin2) {
return (pin2 != null) && (CardProtocol.DefaultPIN2.equals(pin2));
}
public static String getDefaultPIN() {
return CardProtocol.DefaultPIN;
}
public static String getDefaultPIN2() {
return CardProtocol.DefaultPIN2;
}
public static boolean needInit() {
return sharedPreferences==null;
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,23 @@
package com.tangem.domain.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 it is too large Load diff

View file

@ -0,0 +1,405 @@
package com.tangem.domain.wallet;
import android.net.Uri;
import android.util.Log;
import com.google.common.base.Strings;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.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.domain.wallet.FormatUtil.GetDecimalFormat;
/**
* Created by Ilia on 20.03.2018.
*/
public class TokenEngine extends CoinEngine {
public String GetNextNode(TangemCard mCard) {
return "abc1.hsmiths.com";
}
public int GetNextNodePort(TangemCard mCard) {
return 60001;
}
public String GetNode(TangemCard mCard) {
return "abc1.hsmiths.com";
}
public int GetNodePort(TangemCard mCard) {
return 60001;
}
public void SwitchNode(TangemCard mCard) {
}
public boolean AwaitingConfirmation(TangemCard card) {
return false;
}
public boolean InOutPutVisible() {
return false;
}
public String GetBalanceCurrency(TangemCard 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(TangemCard card) {
return card.getTokensDecimal();
}
public String GetContractAddress(TangemCard card) {
return card.getContractAddress();
}
public boolean IsNeedCheckNode() {
return false;
}
public boolean ValdateAddress(String address, TangemCard 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(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard mCard) {
return mCard.getBalance();
}
public boolean IsBalanceAlterNotZero(TangemCard 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(TangemCard 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(TangemCard 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(TangemCard mCard) {
if (!HasBalanceInfo(mCard)) {
return "-- -- -- ";
}
String dec = mCard.getDecimalBalance();
BigDecimal d = convertToEth(dec);
return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
@Override
public String GetBalance(TangemCard mCard) {
if (!HasBalanceInfo(mCard)) {
return "-- -- -- " + GetBalanceCurrency(mCard);
}
String output = GetBalanceValue(mCard);
String s = output + " " + GetBalanceCurrency(mCard);
return s;
}
public String GetBalanceWithAlter(TangemCard mCard) {
//return GetBalance(mCard) + "\n(" + GetBalanceAlterValue(mCard) + " ETH)";
return " " + GetBalance(mCard) + " <br><small><small> + " + GetBalanceAlterValue(mCard) + " ETH for gas</small></small>";
}
public String calculateAddress(TangemCard 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(TangemCard mCard, byte[] bytes) throws Exception {
throw new Exception("Not implemented");
}
@Override
public byte[] ConvertAmountToByteArray(TangemCard mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
@Override
public String GetAmountDescription(TangemCard mCard, String amount) throws Exception {
throw new Exception("Not implemented");
}
public String GetAmountEqualentDescriptor(TangemCard mCard, String value) {
BigDecimal d = new BigDecimal(value);
return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate());
}
public String GetFeeEqualentDescriptor(TangemCard mCard, String value) {
BigDecimal d = new BigDecimal(value);
return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRateAlter());
}
public Uri getShareWalletURIExplorer(TangemCard mCard) {
return Uri.parse("https://etherscan.io/token/" + GetContractAddress(mCard) + "?a=" + mCard.getWallet());
}
public Uri getShareWalletURI(TangemCard mCard) {
return Uri.parse("" + mCard.getWallet());
}
public boolean CheckUnspentTransaction(TangemCard mCard) {
return true;
}
public boolean CheckAmountValie(TangemCard 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(TangemCard 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, TangemCard mCard, CardProtocol protocol) throws Exception {
BigInteger nonceValue = mCard.GetConfirmTXCount();
byte[] pbKey = mCard.getWalletPublicKey();
boolean flag = (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = mCard.getIssuer();
BigInteger fee = new BigInteger(feeValue, 10);
BigDecimal amountDecValue = new BigDecimal(amountValue);
int d = GetTokenDecimals(mCard);
BigDecimal amountDec = new BigDecimal("10");
amountDec = amountDec.pow(d);
amountDec = amountDecValue.multiply(amountDec);
//amountDec = amountDec.multiply(new BigDecimal("1000000000"));
BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10);
//amount = amount.subtract(fee);
BigInteger nonce = nonceValue;
BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000));
BigInteger gasLimit = BigInteger.valueOf(60000);
Integer chainId = ETH_Transaction.ChainEnum.Mainnet.getValue();
BigInteger amountZero = BigInteger.ZERO;
Long multiplicator = 1000000000L;
gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator));
String to = toValue;
if (to.startsWith("0x") || to.startsWith("0X")) {
to = to.substring(2);
}
String contractAddress = GetContractAddress(mCard);
if (contractAddress.startsWith("0x") || contractAddress.startsWith("0X")) {
contractAddress = contractAddress.substring(2);
}
String amountLeadZero = amount.toString(16);
if (amountLeadZero.startsWith("0x") || amountLeadZero.startsWith("0X")) {
amountLeadZero = amountLeadZero.substring(2);
}
while (amountLeadZero.length() < 64) {
amountLeadZero = "0" + amountLeadZero;
}
String cmd = "a9059cbb000000000000000000000000" + to + amountLeadZero; //TODO only for BAT
byte[] data = BTCUtils.fromHex(cmd);
ETH_Transaction tx = ETH_Transaction.create(contractAddress, amountZero, nonce, gasPrice, gasLimit, chainId, data);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
LastSignStorage.setLastSignDate(mCard.getWallet(), new Date());
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if (!f) {
Log.e("ETH-CHECK", "Sign Failed.");
}
tx.signature = new ECDSASignature_ETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
byte[] realTX = tx.getEncoded();
return realTX;
}
}

View file

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

View file

@ -0,0 +1,28 @@
package com.tangem.domain.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;
}
}