Updated on 2026-08-14
This commit is contained in:
parent
61c4f7047d
commit
52ce16bb09
86 changed files with 9207 additions and 9295 deletions
|
|
@ -1,4 +0,0 @@
|
|||
package com.tangem.domain;
|
||||
|
||||
public class Logic {
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.domain.cardReader;
|
||||
|
||||
import com.tangem.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
488
app/src/main/java/com/tangem/domain/wallet/BTCUtils.java
Normal file
488
app/src/main/java/com/tangem/domain/wallet/BTCUtils.java
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
115
app/src/main/java/com/tangem/domain/wallet/Base58.java
Normal file
115
app/src/main/java/com/tangem/domain/wallet/Base58.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
app/src/main/java/com/tangem/domain/wallet/Blockchain.java
Normal file
87
app/src/main/java/com/tangem/domain/wallet/Blockchain.java
Normal 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;
|
||||
}
|
||||
}
|
||||
389
app/src/main/java/com/tangem/domain/wallet/BtcCashEngine.java
Normal file
389
app/src/main/java/com/tangem/domain/wallet/BtcCashEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
524
app/src/main/java/com/tangem/domain/wallet/BtcEngine.java
Normal file
524
app/src/main/java/com/tangem/domain/wallet/BtcEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/com/tangem/domain/wallet/ByteUtil.java
Normal file
39
app/src/main/java/com/tangem/domain/wallet/ByteUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
82
app/src/main/java/com/tangem/domain/wallet/CoinEngine.java
Normal file
82
app/src/main/java/com/tangem/domain/wallet/CoinEngine.java
Normal 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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
270
app/src/main/java/com/tangem/domain/wallet/CryptoUtil.java
Normal file
270
app/src/main/java/com/tangem/domain/wallet/CryptoUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
132
app/src/main/java/com/tangem/domain/wallet/DerEncodingUtil.java
Normal file
132
app/src/main/java/com/tangem/domain/wallet/DerEncodingUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
2091
app/src/main/java/com/tangem/domain/wallet/DeviceName.java
Normal file
2091
app/src/main/java/com/tangem/domain/wallet/DeviceName.java
Normal file
File diff suppressed because it is too large
Load diff
114
app/src/main/java/com/tangem/domain/wallet/Digest.java
Normal file
114
app/src/main/java/com/tangem/domain/wallet/Digest.java
Normal 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();
|
||||
}
|
||||
220
app/src/main/java/com/tangem/domain/wallet/DigestEngine.java
Normal file
220
app/src/main/java/com/tangem/domain/wallet/DigestEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
229
app/src/main/java/com/tangem/domain/wallet/ETH_Transaction.java
Normal file
229
app/src/main/java/com/tangem/domain/wallet/ETH_Transaction.java
Normal 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;
|
||||
}
|
||||
}
|
||||
392
app/src/main/java/com/tangem/domain/wallet/EthEngine.java
Normal file
392
app/src/main/java/com/tangem/domain/wallet/EthEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/tangem/domain/wallet/FormatUtil.java
Normal file
51
app/src/main/java/com/tangem/domain/wallet/FormatUtil.java
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
197
app/src/main/java/com/tangem/domain/wallet/Infura_Request.java
Normal file
197
app/src/main/java/com/tangem/domain/wallet/Infura_Request.java
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
120
app/src/main/java/com/tangem/domain/wallet/Issuer.java
Normal file
120
app/src/main/java/com/tangem/domain/wallet/Issuer.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
39
app/src/main/java/com/tangem/domain/wallet/Keccak256.java
Normal file
39
app/src/main/java/com/tangem/domain/wallet/Keccak256.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
546
app/src/main/java/com/tangem/domain/wallet/KeccakCore.java
Normal file
546
app/src/main/java/com/tangem/domain/wallet/KeccakCore.java
Normal 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);
|
||||
}
|
||||
}
|
||||
145
app/src/main/java/com/tangem/domain/wallet/LastSignStorage.java
Normal file
145
app/src/main/java/com/tangem/domain/wallet/LastSignStorage.java
Normal 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 "";
|
||||
}
|
||||
}
|
||||
285
app/src/main/java/com/tangem/domain/wallet/Logger.java
Normal file
285
app/src/main/java/com/tangem/domain/wallet/Logger.java
Normal 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
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
111
app/src/main/java/com/tangem/domain/wallet/Manufacturer.java
Normal file
111
app/src/main/java/com/tangem/domain/wallet/Manufacturer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
205
app/src/main/java/com/tangem/domain/wallet/PINStorage.java
Normal file
205
app/src/main/java/com/tangem/domain/wallet/PINStorage.java
Normal 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;
|
||||
}
|
||||
}
|
||||
228
app/src/main/java/com/tangem/domain/wallet/RLP.java
Normal file
228
app/src/main/java/com/tangem/domain/wallet/RLP.java
Normal 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;
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/tangem/domain/wallet/RLPElement.java
Normal file
12
app/src/main/java/com/tangem/domain/wallet/RLPElement.java
Normal 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();
|
||||
}
|
||||
37
app/src/main/java/com/tangem/domain/wallet/RLPList.java
Normal file
37
app/src/main/java/com/tangem/domain/wallet/RLPList.java
Normal 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 + ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
23
app/src/main/java/com/tangem/domain/wallet/SharedData.java
Normal file
23
app/src/main/java/com/tangem/domain/wallet/SharedData.java
Normal 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);
|
||||
}
|
||||
}
|
||||
1360
app/src/main/java/com/tangem/domain/wallet/TangemCard.java
Normal file
1360
app/src/main/java/com/tangem/domain/wallet/TangemCard.java
Normal file
File diff suppressed because it is too large
Load diff
405
app/src/main/java/com/tangem/domain/wallet/TokenEngine.java
Normal file
405
app/src/main/java/com/tangem/domain/wallet/TokenEngine.java
Normal 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;
|
||||
}
|
||||
}
|
||||
630
app/src/main/java/com/tangem/domain/wallet/Transaction.java
Normal file
630
app/src/main/java/com/tangem/domain/wallet/Transaction.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue