commit 5e7300987ca12d0953f2b58eeaf245e2387ffbe4 Author: Tangem Date: Mon May 28 13:56:12 2018 +0300 Updated on 2026-08-14 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..5edb4eeb07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000..bab0e7298d Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..30aa626c23 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000000..7ac24c777f --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..dc3456943d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000000..7f68460d8b --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000000..319acbd1d6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,54 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "com.tangem.wallet" + minSdkVersion 23 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" +// versionCode versionBuild +// versionName versionPrefix+"."+getVersionExtentionString(versionBuild) + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +// implementation 'com.android.support:appcompat-v7:27.1.1' +// testImplementation 'junit:junit:4.12' +// androidTestImplementation 'com.android.support.test:runner:1.0.2' +// androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + + + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:27.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.android.support:design:27.1.1' + implementation 'com.android.support:support-compat:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation 'com.madgag.spongycastle:core:1.56.0.0' + implementation 'com.madgag.spongycastle:prov:1.56.0.0' + implementation 'com.google.zxing:core:3.3.2' + implementation 'org.bitcoinj:bitcoinj-parent:0.14.4' + implementation 'org.bitcoinj:bitcoinj-core:0.14.4' + implementation 'com.android.support:cardview-v7:27.1.1' + implementation 'com.android.support:support-v4:27.1.1' + testImplementation 'junit:junit:4.12' + implementation 'me.dm7.barcodescanner:zxing:1.9.8' + implementation 'com.scottyab:rootbeer-lib:0.0.6' + implementation 'com.skyfishjy.ripplebackground:library:1.0.1' + implementation 'info.hoang8f:android-segmented:1.0.6' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..9a7a795e39 --- /dev/null +++ b/app/src/androidTest/java/com/tangem/wallet/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.tangem.wallet + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.tangem.wallet", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6e54fbb6e7 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_fp-web.png b/app/src/main/ic_fp-web.png new file mode 100644 index 0000000000..85a16951a6 Binary files /dev/null and b/app/src/main/ic_fp-web.png differ diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000000..11e69a8089 Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/ic_launcher_round-web.png b/app/src/main/ic_launcher_round-web.png new file mode 100644 index 0000000000..026a409e86 Binary files /dev/null and b/app/src/main/ic_launcher_round-web.png differ diff --git a/app/src/main/ic_logo_small-web.png b/app/src/main/ic_logo_small-web.png new file mode 100644 index 0000000000..76a713ee01 Binary files /dev/null and b/app/src/main/ic_logo_small-web.png differ diff --git a/app/src/main/java/com/tangem/User.java b/app/src/main/java/com/tangem/User.java new file mode 100644 index 0000000000..12caa848b6 --- /dev/null +++ b/app/src/main/java/com/tangem/User.java @@ -0,0 +1,4 @@ +package com.tangem; + +public class User { +} diff --git a/app/src/main/java/com/tangem/cardReader/CardCrypto.java b/app/src/main/java/com/tangem/cardReader/CardCrypto.java new file mode 100644 index 0000000000..ee2c66ce3e --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CardCrypto.java @@ -0,0 +1,243 @@ +package com.tangem.cardReader; + +import android.util.Log; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Created by dvol on 14.11.2017. + */ + +public class CardCrypto { + public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception { + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + KeyFactory factory = KeyFactory.getInstance("EC", "SC"); + + ECPoint p1 = spec.getCurve().decodePoint(publicKeyArray); + ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); + + return factory.generatePublic(keySpec); + } + + public static boolean VerifySignature(byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception { + Signature signatureInstance = Signature.getInstance("SHA256withECDSA"); + PublicKey publicKey = LoadPublicKey(publicKeyArray); + signatureInstance.initVerify(publicKey); + signatureInstance.update(data); + + 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(); + + return signatureInstance.verify(sigDer); + } + + +// public static boolean isCanonical(BigInteger s) { +// +// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); +// +// BigInteger HALF_CURVE_ORDER = spec.getN().shiftRight(1); +// 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. +// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); +// if(!isCanonical(s)) { +// BigInteger canon = spec.getN().subtract(s); +// //Log.e("TX_SIGN", "non Canonical S"); +// return canon; +// } +// +// return s; +// +// } +// +// public static BigInteger[] calcSign2(byte[] priv, byte[] hash) +// { +// ECDSASigner signer = new ECDSASigner(); +// BigInteger d = new BigInteger(priv); +// +// ECNamedCurveParameterSpec CURVE_PARAMS = ECNamedCurveTable.getParameterSpec("secp256k1"); +// ECDomainParameters CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), +// CURVE_PARAMS.getH()); +// ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE); +// //ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); +// signer.init(true, params); +// BigInteger[] rs = signer.generateSignature(hash); +// return rs; +// +// } +// +// public static byte[] Signature3(byte[] privateKeyArray, byte[] data) throws Exception +// { +// byte[] hash=Util.calculateSHA256(data); +// BigInteger[] signBI=calcSign2(privateKeyArray, hash); +// //signBI[0]=toCanonicalised(signBI[0]); +// signBI[1]=toCanonicalised(signBI[1]); +// byte[] r = signBI[0].toByteArray(); +// byte[] s = signBI[1].toByteArray(); +// +// byte[] res = new byte[64]; +// if( r.length==32 ) { +// System.arraycopy(r, 0, res, 0, r.length); +// }else if( r.length==33 && r[0]==0 ){ +// System.arraycopy(r, 1, res, 0, r.length-1); +// }else { +// throw new Exception("unsupported r-length"); +// } +// if( s.length==32 ) { +// System.arraycopy(s, 0, res, 32, 32); +// }else{ +// throw new Exception("unsupported s-length"); +// } +// return res; +// } + + public static byte[] Signature(byte[] privateKeyArray, byte[] data) throws Exception { + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + KeyFactory factory = KeyFactory.getInstance("EC", "SC"); + + ECPrivateKeySpec keySpecP = new ECPrivateKeySpec(new BigInteger(1,privateKeyArray), spec); + + Signature signature = Signature.getInstance("SHA256withECDSA"); + + PrivateKey privateKey = factory.generatePrivate(keySpecP); + signature.initSign(privateKey); + signature.update(data); + byte[] enc = signature.sign(); + + if (enc[0] != 0x30) throw new Exception("bad encoding 1"); + if ((enc[1] & 0x80) != 0) throw new Exception("unsupported length encoding 1"); + if (enc[2] != 0x02) throw new Exception("bad encoding 2"); + if ((enc[3] & 0x80) != 0) throw new Exception("unsupported length encoding 2"); + int rLength = enc[3]; + + if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3"); + if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3"); + int sLength = enc[5 + rLength]; + + + int sPos = 6 + rLength; + byte[] res = new byte[64]; + if (rLength <= 32) { + System.arraycopy(enc, 4, res, 32-rLength, rLength); + rLength=32; + } else if (rLength == 33 && enc[4] == 0) { + rLength--; + System.arraycopy(enc, 5, res, 0, rLength); + } else { + Log.e("cardCrypto","r-length:" + String.valueOf(rLength)); + Log.e("cardCrypto","s-length:" + String.valueOf(sLength)); + 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) { + System.arraycopy(enc, sPos, res, rLength+32-sLength, sLength); + sLength=32; + } else if (sLength == 33 && enc[sPos] == 0) { + System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1); + } else { + Log.e("cardCrypto","s-length:" + String.valueOf(sLength)); + Log.e("cardCrypto","r-length:" + String.valueOf(rLength)); + Log.e("cardCrypto","enc:" +Util.bytesToHex(enc)); + throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)); + } + + if(!VerifySignature(GeneratePublicKey(privateKeyArray), data, res)) + { + throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)+",res:"+Util.bytesToHex(res)); + } + + return res; + } + + public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws NoSuchProviderException, NoSuchAlgorithmException { + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + + + byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1,privateKeyArray)).getEncoded(false); + + return publicKeyArray; + } + + /** + * Computes the PBKDF2 hash of a password. + * + * @param password the password to hash. + * @param salt the salt + * @param iterations the iteration count (slowness factor) + * @return the PBDKF2 hash of the password + */ + public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations) + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + return PBKDF2.deriveKey(password, salt, iterations); + } + + public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException + { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); + byte[] mEncryptedData = cipher.doFinal(data); + return mEncryptedData; + } + + public static byte[] Decrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + try { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); + byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length )); + return decryptedData; + } + catch (Exception e) + { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING"); + Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); + byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length )); + Log.e("decrypt",Util.bytesToHex(decryptedData)); + throw e; + } + } +} diff --git a/app/src/main/java/com/tangem/cardReader/CardProtocol.java b/app/src/main/java/com/tangem/cardReader/CardProtocol.java new file mode 100644 index 0000000000..3e0a9f1062 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CardProtocol.java @@ -0,0 +1,954 @@ +package com.tangem.cardReader; + +import android.content.Context; +import android.nfc.Tag; +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 org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.interfaces.ECPublicKey; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.math.ec.ECPoint; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Calendar; + +import javax.crypto.KeyAgreement; + + +/** + * Created by dvol on 14.07.2017. + */ + +public class CardProtocol { + + + public interface Notifications { + void OnReadStart(CardProtocol cardProtocol); + + void OnReadProgress(CardProtocol cardProtocol, int progress); + + void OnReadFinish(CardProtocol cardProtocol); + + void OnReadCancel(); + + void OnReadWait(int msec); + + void OnReadBeforeRequest(int timeout); + + void OnReadAfterRequest(); + } + + + public void setError(Exception error) { + mError = error; + } + + public Tangem_Card getCard() { + return mCard; + } + + public Exception getError() { + return mError; + } + + private static final String logTag = "CardProtocol"; + public static final int SW_PIN_ERROR = SW.INVALID_PARAMS; + + public static final String DefaultPIN = "000000"; + public static final String DefaultPIN2 = "000"; + + protected IsoDep mIsoDep; + + public Tag getTag() { + return mIsoDep.getTag(); + } + + protected String mPIN; + protected Notifications mNotifications; + + public void setPIN(String PIN) { + mPIN = PIN; + if (mCard != null) { + mCard.setPIN(PIN); + } + } + + protected Tangem_Card mCard; + protected Exception mError; + protected Context mContext; + + static { + Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); + } + + public CardProtocol(Context context, IsoDep isoDep, Notifications notifications) { + mContext = context; + mIsoDep = isoDep; + mNotifications = notifications; + mPIN = null; + mCard = new Tangem_Card(Util.byteArrayToHexString(mIsoDep.getTag().getId())); + } + + public CardProtocol(Context context, IsoDep isoDep, Tangem_Card card, Notifications notifications) { + mContext = context; + mIsoDep = isoDep; + mNotifications = notifications; + mPIN = card.getPIN(); + mCard = card; + } + + public static class TangemException extends Exception { + public TangemException(String message) { + super(message); + } + } + + public static class TangemException_InvalidPIN extends TangemException { + public TangemException_InvalidPIN(String message) { + super(message); + } + } + + public static class TangemException_NeedPause extends TangemException { + public TangemException_NeedPause(String message) { + super(message); + } + } + + public static class TangemException_ExtendedLengthNotSupported extends TangemException { + public TangemException_ExtendedLengthNotSupported(String message) { + super(message); + } + } + + + public byte[] GetUID() { + if (mIsoDep == null || mIsoDep.getTag() == null) return null; + return mIsoDep.getTag().getId(); + } + + public int getTimeout() { + if (mIsoDep == null || mIsoDep.getTag() == null) return 60000; + return mIsoDep.getTimeout(); + } + + + protected byte[] protocolKey; + + public void resetProtocolKey() { + protocolKey = null; + } + + public void CreateProtocolKey() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + protocolKey = CardCrypto.pbkdf2(Util.calculateSHA256(mPIN), mIsoDep.getTag().getId(), 50); + //Log.e("Reader", String.format("PIN: %s, Protocol key: %s", mPIN, Util.bytesToHex(protocolKey))); + if (sessionKey != null) { + sessionKey = null; + } + } + + byte[] sessionKey = null; + + public void run_OpenSession(Tangem_Card.EncryptionMode encryptionMode) throws Exception { + sessionKey = null; + try { + CommandApdu cmdApdu = new CommandApdu(CommandApdu.ISO_CLA, INS.OpenSession.Code, 0, encryptionMode.getP()); + + switch (encryptionMode) { + case Fast: { + byte[] baMyChallenge = Util.generateRandomBytes(16); + + cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyChallenge); + Log.i(logTag, cmdApdu.getCommandName()); + ResponseApdu rspApdu = null; + + try { + if (mIsoDep == null) { + throw new TagLostException(); + } + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); + + byte[] rsp = mIsoDep.transceive(cmdBytes); + rspApdu = new ResponseApdu(rsp); + + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + + if (rspApdu.isParsedWithError()) { + throw new Exception("Can't parse answer"); + } + } catch (Exception E) { + sessionKey = null; + throw E; + } + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + byte[] baTheirsChallenge = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value; + if (protocolKey == null) CreateProtocolKey(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(baMyChallenge); + outputStream.write(baTheirsChallenge); + outputStream.write(protocolKey); + sessionKey = Util.calculateSHA256(outputStream.toByteArray()); + //Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey))); + } else { + Log.e(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description())); + throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2())); + } + } + break; + case Strong: { + KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "SC"); + kpgen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom()); + KeyPair KP = kpgen.generateKeyPair(); + KeyAgreement ka = KeyAgreement.getInstance("ECDH", "SC"); + ka.init(KP.getPrivate()); + + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + //return spec.getG().multiply(new BigInteger((ECPrivateKey) )).getEncoded(false); + ECPublicKey eckey = (ECPublicKey) KP.getPublic(); + byte[] baMyPublicKey = eckey.getQ().getEncoded(false); + + cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyPublicKey); + Log.i(logTag, cmdApdu.getCommandName()); + ResponseApdu rspApdu = null; + + try { + if (mIsoDep == null) { + throw new TagLostException(); + } + //mIsoDep.setTimeout(msTimeout); + + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); + byte[] rsp = mIsoDep.transceive(cmdBytes); + rspApdu = new ResponseApdu(rsp); + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + + if (rspApdu.isParsedWithError()) { + throw new Exception("Can't parse answer"); + } + } catch (Exception E) { + sessionKey = null; + throw E; + } + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + byte[] baTheirsPublicKey = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value; + ka.doPhase(CardCrypto.LoadPublicKey(baTheirsPublicKey), true); + if (protocolKey == null) CreateProtocolKey(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(ka.generateSecret()); + outputStream.write(protocolKey); + sessionKey = Util.calculateSHA256(outputStream.toByteArray()); +// Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey))); + } else { + Log.i(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description())); + throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2())); + } + } + break; + default: + throw new Exception("Unknown encryption mode"); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, String.format("Exception: %s", e.getMessage())); + throw new Exception("Can't open session: " + e.getMessage()); + } + } + + + // 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 (sessionKey == null) { + run_OpenSession(mCard.encryptionMode); + } + cmdApdu.Crypt(sessionKey); + } + cmdApdu.setP1(mCard.encryptionMode.getP()); + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); + byte[] rsp; + ResponseApdu rspApdu; + try { + do { + try { + mNotifications.OnReadBeforeRequest(mIsoDep.getTimeout()); + try { + rsp = mIsoDep.transceive(cmdBytes); + } finally { + mNotifications.OnReadAfterRequest(); + } + } catch (IOException e) { + if (e.getMessage().contains("length exceeds supported maximum") && mIsoDep.isExtendedLengthApduSupported()) { + throw new TangemException_ExtendedLengthNotSupported(e.getMessage()); + } + throw e; + } + if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) { + rspApdu = ResponseApdu.Decrypt(rsp, sessionKey); + } else { + rspApdu = new ResponseApdu(rsp); + } + + if (rspApdu.isParsedWithError()) { + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + throw new TangemException(rspApdu.getParseErroMessage()); + } else if (rspApdu.getSW1SW2() == SW.NEED_PAUSE && mNotifications != null) { + int remainingPause = rspApdu.getTLVs().getTagAsInt(TLV.Tag.TAG_Pause) * 10; + Log.v("NFC", String.format(">> Security delay, remaining %f s", remainingPause / 1000.0)); + if (breakOnNeedPause) { + break; + } else { + mNotifications.OnReadWait(remainingPause); + } + } else { + Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); + } + } while (rspApdu.getSW1SW2() == SW.NEED_PAUSE); + } finally { + mNotifications.OnReadWait(0); + } + + return rspApdu; + } + + private CommandApdu StartPrepareCommand(INS ins) throws NoSuchAlgorithmException { + CommandApdu Apdu = new CommandApdu(ins); + byte[] baPIN = Util.calculateSHA256(mPIN); + Apdu.addTLV(TLV.Tag.TAG_PIN, baPIN); + if (ins != INS.Read) { + Apdu.addTLV(TLV.Tag.TAG_CardID, mCard.getCID()); + } + return Apdu; + } + + public void run_Read() throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.Read); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + + + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + + + readResult = rspApdu.getTLVs(); + + parseReadResult(); + + run_ReadOrWriteIssuerDataAndDefineOfflineBalance(); + + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible PIN is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("FAILED: [%04X]\n", rspApdu.getSW1SW2())); + } + } + + public void run_ReadOrWriteIssuerDataAndDefineOfflineBalance() throws Exception { + TLVList tlvIssuerData; + if (mCard.getNeedWriteIssuerData()) { + run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature()); + try { + tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData()); + } catch (TLVException e) { + e.printStackTrace(); + tlvIssuerData = null; + } + } else { + try { + tlvIssuerData = run_ReadIssuerData(); + } catch (Exception e) { + e.printStackTrace(); + tlvIssuerData = null; + } + } + if (mCard.getStatus() == Tangem_Card.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); + } else { + mCard.clearOfflineBalance(); + } + } else { + mCard.clearOfflineBalance(); + } + } + + private TLVList readResult = null; + + public void clearReadResult() { + sessionKey = null; + readResult = null; + } + + public boolean haveReadResult() { + return readResult != null; + } + + 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))); + 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) { + try { + TLV tlvCardPubkicKey = readResult.getTLV(TLV.Tag.TAG_CardPublicKey); + if (tlvCardPubkicKey == null) + throw new TangemException("Invalid answer format"); + mCard.setCardPublicKey(tlvCardPubkicKey.Value); + + TLVList tlvCardData = TLVList.fromBytes(readResult.getTLV(TLV.Tag.TAG_CardData).Value); + + TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol); + TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address); + TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal); + + if (tokenSymbol != null) + mCard.setTokenSymbol(tokenSymbol.getAsString()); + + if (contractAddress != null) + mCard.setContractAddress(contractAddress.getAsString()); + + if (tokens_decimal != null) + mCard.setTokensDecimal(tokens_decimal.getAsInt()); + + // this method has reflection TokenSymbol + // you mast call setBlockchainIDFromCard after calling setTokensDecimal + mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString()); + byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value; + int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF); + int month = tlvPersonalizationDT[2] - 1; + int day = tlvPersonalizationDT[3]; + Calendar cd = Calendar.getInstance(); + cd.set(year, month, day, 0, 0, 0); + mCard.setPersonalizationDateTime(cd.getTime()); + try { + if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) { + mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString()); + } else { + mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString()); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Cannot get firmware version"); + mCard.setFirmwareVersion("0.00"); + } + + try { + if (mCard.getFirmwareVersion().compareTo("1.05") < 0) { + mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value)); + } else { + mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value)); + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Cannot get issuer, try a version for older cards"); + try { + mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value)); + } catch (Exception ee) { + ee.printStackTrace(); + Log.e(logTag, "Cannot get issuer"); + mCard.setIssuer(Issuer.Unknown); + } + } + + try { + mCard.setSettingsMask(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Can't get settings mask"); + } + + if (readResult.getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) { + mCard.setPauseBeforePIN2(10 * readResult.getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2)); + } + + try { + mCard.setSigningMethod(readResult.getTagAsInt(TLV.Tag.TAG_SigningMethod)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Can't get signing method"); + mCard.setSigningMethod(0); + } + + try { + mCard.setMaxSignatures(readResult.getTagAsInt(TLV.Tag.TAG_MaxSignatures)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(logTag, "Can't get max signatures"); + } + + } catch (Exception e) { + e.printStackTrace(); + throw new TangemException("Can't parse card data"); + } + } + + + if (mCard.getStatus() == Tangem_Card.Status.Loaded) { + + TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey); + + ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); + ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value); + + byte pkUncompressed[] = p1.getEncoded(false); + + byte pkCompresses[] = p1.getEncoded(true); + mCard.setWalletPublicKey(pkUncompressed); + mCard.setWalletPublicKeyRar(pkCompresses); + + CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); + String wallet = engineCoin.calculateAddress(mCard, pkUncompressed); + mCard.setWallet(wallet); + //mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed)); + + mCard.setRemainingSignatures(readResult.getTagAsInt(TLV.Tag.TAG_RemainingSignatures)); + + } else { + mCard.setWallet("N/A"); + } + } + + + public TLVList run_VerifyCard() throws Exception { + if (mCard.getCardPublicKey() == null || readResult == null) { + run_Read(); + } + if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) { + getCard().setManufacturer(Manufacturer.Unknown, false); + return null; + } + + if (mCard.getCardPublicKey() == null) { + throw new TangemException("Not all data read, can't verify card!"); + } + + CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCard); + byte[] bChallenge = Util.generateRandomBytes(16); + rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList verifyResult = rspApdu.getTLVs(); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + verifyResult.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge)); + + TLV tlvSalt = verifyResult.getTLV(TLV.Tag.TAG_Salt); + TLV tlvCardSignature = verifyResult.getTLV(TLV.Tag.TAG_CardSignature); +// TLV tlvManufacturerSignature = verifyResult.getTLV(TLV.Tag.TAG_Manufacturer_Signature); + + if (tlvSalt == null || tlvCardSignature == null) { + throw new TangemException("Not all data read, can't verify card!"); + } + + try { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + bs.write(bChallenge); + bs.write(tlvSalt.Value); + byte[] dataArray = bs.toByteArray(); + if (CardCrypto.VerifySignature(mCard.getCardPublicKey(), dataArray, tlvCardSignature.Value)) { + getCard().setCardPublicKeyValid(true); + Log.i(logTag, "Card signature verification OK"); + } else { + Log.e(logTag, "Card signature verification FAILED"); + getCard().setCardPublicKeyValid(false); + } + + //getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString(), bChallenge, tlvSalt.Value, tlvManufacturerSignature.Value), true); + getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true); + } catch (Exception e) { + e.printStackTrace(); + getCard().setManufacturer(Manufacturer.Unknown, false); + } + return verifyResult; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + getCard().setManufacturer(Manufacturer.Unknown, false); + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + + } + + public void run_CreateWallet(String PIN2) throws Exception { + if (readResult == null) run_Read(); + CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public TLVList run_CheckWallet() throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.CheckWallet); + byte[] bChallenge = Util.generateRandomBytes(16); + rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList Result = rspApdu.getTLVs(); + Result.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge)); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + return Result; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_CheckWalletWithSignatureVerify() throws Exception { + if (mCard.getCardPublicKey() == null || readResult == null) { + run_Read(); + } + if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) { + getCard().setManufacturer(Manufacturer.Unknown, false); + return; + } + + if (readResult.getTagAsInt(TLV.Tag.TAG_Status) != Tangem_Card.Status.Loaded.getCode()) { + throw new TangemException("Card must be loaded"); + } + TLVList checkResult = run_CheckWallet(); + if (checkResult == null) return; + + TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey); + TLV tlvChallenge = checkResult.getTLV(TLV.Tag.TAG_Challenge); + TLV tlvSalt = checkResult.getTLV(TLV.Tag.TAG_Salt); + TLV tlvSignature = checkResult.getTLV(TLV.Tag.TAG_Signature); + + if (tlvPublicKey == null || tlvChallenge == null || tlvSalt == null || tlvSignature == null) { + throw new TangemException("Not all data read, can't check signature!"); + } + + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + bs.write(tlvChallenge.Value); + bs.write(tlvSalt.Value); + byte[] dataArray = bs.toByteArray(); + + if (CardCrypto.VerifySignature(tlvPublicKey.Value, dataArray, tlvSignature.Value)) { + Log.i(logTag, "Signature verification OK"); + mCard.setWalletPublicKeyValid(true); + } else { + mCard.setWalletPublicKeyValid(false); + } + } + + public void run_PurgeWallet(String PIN2) throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.PurgeWallet); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else { + + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_SwapPIN(String PIN2, String newPin, String newPin2, boolean breakOnNeedPause) throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.SwapPIN); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + rqApdu.addTLV(TLV.Tag.TAG_NewPIN, Util.calculateSHA256(newPin)); + rqApdu.addTLV(TLV.Tag.TAG_NewPIN2, Util.calculateSHA256(newPin2)); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, breakOnNeedPause); + + if (rspApdu.isStatus(SW.PIN1_CHANGED) || rspApdu.isStatus(SW.PIN2_CHANGED) || rspApdu.isStatus(SW.PINS_CHANGED) || rspApdu.isStatus(SW.PINS_NOT_CHANGED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (newPin2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } else { + mCard.setUseDefaultPIN2(false); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); + } else if (breakOnNeedPause && rspApdu.isStatus(SW.NEED_PAUSE)) { + throw new TangemException_NeedPause(String.format("FAILED: [%04X] - Need pause\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_CheckPIN2isDefault() throws Exception { + if (mCard.isFirmwareNewer("1.19") || (mCard.isFirmwareNewer("1.12") && (mCard.getPauseBeforePIN2() == 0 || mCard.useSmartSecurityDelay()))) { + // can obtain SwapPIN(to default) answer without security delay - try check if PIN2 is default with card request + try { + run_SwapPIN(DefaultPIN2, mPIN, DefaultPIN2, true); + mCard.setUseDefaultPIN2(true); + } catch (TangemException_NeedPause e) { + mCard.setUseDefaultPIN2(null); + } catch (TangemException_InvalidPIN e) { + mCard.setUseDefaultPIN2(false); + } + } else { + mCard.setUseDefaultPIN2(null); + } + } + + public TLVList run_SignHashes(String PIN2, byte[][] hashes, boolean UseIssuerValidation, byte[] issuerData, Issuer issuer) throws Exception { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + if (hashes.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < hashes.length; i++) { + if (i != 0 && hashes[0].length != hashes[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(hashes[i]); + } + CommandApdu rqApdu = StartPrepareCommand(INS.Sign); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + rqApdu.addTLV_U8(TLV.Tag.TAG_TrOut_HashSize, hashes[0].length); + rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, bs.toByteArray()); + if (UseIssuerValidation) { + byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), bs.toByteArray()); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerSignature); + } + if (issuerData != null) { + if (issuer == null || issuer == Issuer.Unknown) + throw new Exception("Need known Issuer to write issuer Data"); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); + byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), issuerData); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature); + } + + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList Result = rspApdu.getTLVs(); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + return Result; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public TLVList run_SignRaw(String PIN2, byte[] bTxOutData) throws Exception { + + CommandApdu rqApdu = StartPrepareCommand(INS.Sign); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + rqApdu.addTLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData); + rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, "sha-256x2".getBytes("US-ASCII")); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + TLVList Result = rspApdu.getTLVs(); + Result.add(new TLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData)); + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + return Result; + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception { + if (readResult == null) run_Read(); + CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode); + rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII")); + rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress); + rqApdu.addTLV_U16(TLV.Tag.TAG_CodePageCount, codePageCount); + rqApdu.addTLV(TLV.Tag.TAG_Challenge, challenge); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + return rspApdu.getTLVs().getTLV(TLV.Tag.TAG_CodeHash).Value; + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private void run_ValidateCard(String PIN2) throws Exception { + if (readResult == null) run_Read(); + CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard); + rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(true); + } + } else if (rspApdu.isStatus(SW_PIN_ERROR)) { + if (PIN2.equals(DefaultPIN2)) { + mCard.setUseDefaultPIN2(false); + } + throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception { + run_Read(); + + CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); + rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + private TLVList run_ReadIssuerData() throws Exception { + CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData); + + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); + + if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data); + TLV issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature); + + if (issuerData == null || issuerDataSignature == null) + throw new TangemException("Invalid answer format (GetIssuerData)"); + + ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream(); + bsDataToVerify.write(mCard.getCID()); + bsDataToVerify.write(issuerData.Value); + try { + if (CardCrypto.VerifySignature(mCard.getIssuer().getPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) { + mCard.setIssuerData(issuerData.Value, issuerDataSignature.Value); + return TLVList.fromBytes(issuerData.Value); + } else { + throw new TangemException("Invalid issuer data read (signature verification failed)"); + } + } catch (Exception e) { + e.printStackTrace(); + throw new TangemException("Invalid issuer data read"); + } + } else { + throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); + } + } + + public void run_GetSupportedEncryption() throws Exception { + mCard.encryptionMode = Tangem_Card.EncryptionMode.None; + do { + CommandApdu rqApdu = StartPrepareCommand(INS.Read); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); + + 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; + } else { + throw new Exception("Can't get supported encryption methods"); + } + } else if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { + Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); + readResult = rspApdu.getTLVs(); + mCard.setPIN(mPIN); + break; + } else { + break; + } + } while (true); + } + +} diff --git a/app/src/main/java/com/tangem/cardReader/CommandApdu.java b/app/src/main/java/com/tangem/cardReader/CommandApdu.java new file mode 100644 index 0000000000..3d69198317 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CommandApdu.java @@ -0,0 +1,277 @@ +package com.tangem.cardReader; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class CommandApdu { + + public static final byte ISO_CLA = (byte) 0x00; + + protected String mCmdName; + protected int mCla = 0x00; + protected int mIns = 0x00; + protected int mP1 = 0x00; + protected int mP2 = 0x00; + protected int mLc = 0x00; + + protected byte[] mData = new byte[0]; + + protected int mLe = 0x00; + protected boolean mLeUsed = false; + protected TLVList tlvList = new TLVList(); + + public CommandApdu() { + } + + public CommandApdu(int cla, int ins, int p1, int p2) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mP1 = p1; + mP2 = p2; + } + + public CommandApdu(int cla, int ins, int p1, int p2, byte[] data) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mLc = data.length; + mP1 = p1; + mP2 = p2; + mData = data; + } + + public CommandApdu(INS ins) { + setCommandName(ins.name()); + mCla = ISO_CLA; + mIns = ins.Code; + mP1 = 0; + mP2 = 0; + } + + public CommandApdu(int cla, int ins, int p1, int p2, byte[] data, int le) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mLc = data.length; + mP1 = p1; + mP2 = p2; + mData = data; + mLe = le; + mLeUsed = true; + } + + public CommandApdu(int cla, int ins, int p1, int p2, int le) { + setCommandName(ins); + mCla = cla; + mIns = ins; + mP1 = p1; + mP2 = p2; + mLe = le; + mLeUsed = true; + } + + public void setCommandName(String cmdName) { + mCmdName = cmdName; + } + + private void setCommandName(int ins) { + INS ins1 = INS.ByCode(ins); + if (ins1 != null) { + mCmdName = ins1.toString(); + } else { + mCmdName = String.format("INS[%2X]", ins); + } + } + + public String getCommandName() { + return mCmdName; + } + + public void setP1(int p1) { + mP1 = p1; + } + + public void setP2(int p2) { + mP2 = p2; + } + + public void setData(byte[] data) { + mLc = data.length; + mData = data; + } + + public void addTLV(TLV.Tag tag, byte[] value) { + tlvList.add(new TLV(tag, value)); + } + + public void addTLV_U8(TLV.Tag tag, int U8) { + addTLV(tag, new byte[]{(byte) U8}); + } + + public void addTLV_U16(TLV.Tag tag, int U16) { + addTLV(tag, Util.intToByteArray2(U16)); + } + + public void addTLV_U32(TLV.Tag tag, int U32) { + addTLV(tag, Util.intToByteArray4(U32)); + } + + public void setLe(int le) { + mLe = le; + mLeUsed = true; + } + + public int getP1() { + return mP1; + } + + public int getP2() { + return mP2; + } + + public int getLc() { + return mLc; + } + + public byte[] getData() { + return mData; + } + + public TLVList getTLVs() { + return tlvList; + } + + public int getLe() { + return mLe; + } + + public static String toString(byte[] cmdApdu, int Lc) { + String cmd = Util.bytesToHex(cmdApdu); + if (Lc == 0) return cmd; + return cmd.substring(0, 8) + " " + cmd.substring(8, 10) + " " + + cmd.substring(10, 10 + Lc * 2) + " " + cmd.substring(10 + Lc * 2, cmd.length()); + } + + + public void Crypt(byte[] key) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException { + if (tlvList.size() != 0) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + for (TLV tlv : tlvList) { + try { + tlv.WriteToStream(stream); + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + + mData = stream.toByteArray(); + byte[] crc = Util.calculateCRC16(mData); + stream = new ByteArrayOutputStream(); + stream.write(Util.intToByteArray2(mData.length)); + stream.write(crc); + stream.write(mData); + mData = stream.toByteArray(); + + byte[] mEncryptedData = CardCrypto.Encrypt(key, mData); + + mData = mEncryptedData; + mLc = mData.length; + + tlvList.clear(); + + } + + } + + public byte[] toBytes() { + int length = 4; // CLA, INS, P1, P2 + + if (tlvList.size() != 0) { + mData = tlvList.toBytes(); + mLc = mData.length; + } + + if (mData.length != 0) { + length += 1; // LC + if (mLc >= 256) + length += 2; + length += mData.length; // DATA + } + if (mLeUsed) { + length += 1; // LE + if (mLc >= 256) + length += 2; + } + + byte[] apdu = new byte[length]; + + int index = 0; + apdu[index] = (byte) mCla; + index++; + apdu[index] = (byte) mIns; + index++; + apdu[index] = (byte) mP1; + index++; + apdu[index] = (byte) mP2; + index++; + if (mLc != 0) { + if (mLc < 256) { + apdu[index] = (byte) mLc; + index++; + } else { + apdu[index] = 0; + index++; + apdu[index] = (byte) (mLc >> 8); + index++; + apdu[index] = (byte) (mLc & 0xFF); + index++; + } + + System.arraycopy(mData, 0, apdu, index, mData.length); + index += mData.length; + } + if (mLeUsed) { + if (mLc < 256) { + apdu[index] += (byte) mLe; // LE + } else { + apdu[index] = 0; + index++; + apdu[index] = (byte) (mLe >> 8); + index++; + apdu[index] = (byte) (mLe & 0xFF); + index++; + } + } + + return apdu; + } + + public CommandApdu clone() { + CommandApdu apdu = new CommandApdu(); + apdu.setCommandName(mCmdName); + apdu.mCla = mCla; + apdu.mIns = mIns; + apdu.mP1 = mP1; + apdu.mP2 = mP2; + apdu.mLc = mLc; + apdu.mData = new byte[mData.length]; + System.arraycopy(mData, 0, apdu.mData, 0, mData.length); + apdu.mLe = mLe; + apdu.mLeUsed = mLeUsed; + apdu.tlvList = new TLVList(tlvList); + return apdu; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/CustomCardReader.java b/app/src/main/java/com/tangem/cardReader/CustomCardReader.java new file mode 100644 index 0000000000..9c2f9437b6 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/CustomCardReader.java @@ -0,0 +1,86 @@ +package com.tangem.cardReader; + +import android.content.Context; +import android.nfc.TagLostException; +import android.nfc.tech.IsoDep; + +import java.io.IOException; + +public class CustomCardReader implements Runnable { + + protected NfcManager mNfcManager; + + public interface UiCallbacks { + // display console messages + void onMessageSend(String raw, String name); + void onMessageRcv(String raw, String name); + void onOkay(String message); + void onError(String message); + void onStart(String message); + void onAction(String message, String name); + void onSeparator(); + + // clear console messages + void clearMessages(); + + // ui listeners + void setUserSelectListener(UiListener callback); + + // cleanup, if needed + void onFinish(boolean err); + } + + public interface UiListener { + void onUserSelect(String aid); + } + + public static final int SW_NO_ERROR = 0x9000; + public static final int SW_GET_RESPONSE = 0x6700; + + protected IsoDep mIsoDep; + protected UiCallbacks mUiCallbacks; + + protected String mAid; + protected byte[] mAidBytes; + + protected Context mContext; + + public CustomCardReader(Context context, NfcManager manager, IsoDep isoDep, String aid, UiCallbacks uiCallbacks) { + this.mIsoDep = isoDep; + this.mAid = aid; + this.mAidBytes = Util.hexToBytes(aid); + this.mUiCallbacks = uiCallbacks; + this.mNfcManager = manager; + this.mContext = context; + } + + // send command APDU, get response APDU, and display HEX data to user + protected ResponseApdu sendAndRcv(CommandApdu cmdApdu) + throws TagLostException, IOException { + byte[] cmdBytes = cmdApdu.toBytes(); + String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); + //mUiCallbacks.onMessageSend(cmdStr, cmdApdu.getCommandName()); + byte[] rsp = mIsoDep.transceive(cmdBytes); + ResponseApdu rspApdu = new ResponseApdu(rsp); + byte[] data = rspApdu.getData(); + + //mUiCallbacks.onMessageRcv(Util.bytesToHex(rsp), cmdApdu.getCommandName()); + + if (rspApdu.isParsedWithError()) { + mUiCallbacks.onError(rspApdu.getParseErroMessage()); + } + + /* + Log.d(TAG, "response APDU: " + Util.bytesToHex(rsp)); + if (data.length > 0) { + Log.d(TAG, TLVUtil.prettyPrintAPDUResponse(data)); + } + */ + return rspApdu; + } + + + @Override + public void run() { + } +} diff --git a/app/src/main/java/com/tangem/cardReader/INS.java b/app/src/main/java/com/tangem/cardReader/INS.java new file mode 100644 index 0000000000..8212ff802c --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/INS.java @@ -0,0 +1,43 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 07.03.2018. + */ +public enum INS { + Unknown(0x00), + BootROM_SOS(0x40), + BootROM_Tangem(0xF0), + Personalize(0xF1), + Read(0xF2), + VerifyCard(0xF3), + ValidateCard(0xF4), + VerifyCode(0xF5), + WriteIssuerData(0xF6), + GetIssuerData(0xF7), + CreateWallet(0xF8), + CheckWallet(0xF9), + SwapPIN(0xFA), + Sign(0xFB), + PurgeWallet(0xFC), + Activate(0xFE), + OpenSession(0xFF), + ReadBlockedData(0xE4), + CreateTestWallet(0xE0), + ExtractWalletKey(0xE1), + Test(0xE2), + Depersonalize(0xE3); + + INS(int Code) { + this.Code = Code; + } + + public int Code; + + public static INS ByCode(int Code) { + INS[] allINS = INS.values(); + for (INS i : allINS) { + if (i.Code == Code) return i; + } + return Unknown; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java b/app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java new file mode 100644 index 0000000000..5eb2fd2413 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java @@ -0,0 +1,45 @@ +package com.tangem.cardReader; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v7.app.AlertDialog; + +import com.tangem.wallet.R; + +/** + * Created by dvol on 18.02.2018. + */ + +public class NFCEnableDialog extends DialogFragment { + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setCancelable(false) + .setIcon(R.drawable.ic_action_nfc_gray) + .setTitle(R.string.nfc_disabled) + .setMessage(R.string.enable_nfc) + .setPositiveButton(R.string.dialog_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + // take user to wireless settings + getActivity().startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)); + } + }) + .setNegativeButton(R.string.dialog_quit, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + dialog.cancel(); + getActivity().finish(); + } + }); + return builder.create(); + } +} diff --git a/app/src/main/java/com/tangem/cardReader/NfcManager.java b/app/src/main/java/com/tangem/cardReader/NfcManager.java new file mode 100644 index 0000000000..7debf041aa --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/NfcManager.java @@ -0,0 +1,153 @@ +package com.tangem.cardReader; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.util.Log; + +import java.io.IOException; + +public class NfcManager { + + private static final String TAG = "NfcManager"; + + + // reader mode flags: listen for type A (not B), skipping ndef check + private static final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS; + + private NfcAdapter mNfcAdapter; + private NFCEnableDialog mEnableNfcDialog; + private Activity mActivity; + private NfcAdapter.ReaderCallback mReaderCallback; + + private boolean broadcomWorkaround = false; + private static final int DELAY_PRESENCE = 1500; + + public NfcManager(Activity activity, NfcAdapter.ReaderCallback readerCallback) { + mActivity = activity; + mReaderCallback = readerCallback; + mNfcAdapter = NfcAdapter.getDefaultAdapter(activity); + } + + public void onResume() { + // register broadcast receiver + IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + mActivity.registerReceiver(mBroadcastReceiver, filter); + + if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { + ShowNFCEnableDialog(); + + } else { + enableReaderMode(); + } + } + + public void ShowNFCEnableDialog() { + mEnableNfcDialog=new NFCEnableDialog(); + mEnableNfcDialog.show(mActivity.getFragmentManager(),"NFCEnableDialog"); + } + + public void onPause() { + mActivity.unregisterReceiver(mBroadcastReceiver); + disableReaderMode(); + } + + public void onStop() { + if (mEnableNfcDialog != null) { + mEnableNfcDialog.dismiss(); + } + } + + public void IgnoreTag(Tag tag) throws IOException { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { +// mNfcAdapter.ignore(tag, 500, null, null); +// }else{ + IsoDep isoDep = IsoDep.get(tag); + if (isoDep != null) { + isoDep.close(); + } +// } + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @SuppressWarnings("deprecation") + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) + return; + if (action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) { + int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, + NfcAdapter.STATE_ON); + if (state == NfcAdapter.STATE_ON + || state == NfcAdapter.STATE_TURNING_ON) { + Log.d(TAG, "state: " + state + " , dialog: " + + mEnableNfcDialog); + if (mEnableNfcDialog != null) { + mEnableNfcDialog.dismiss(); + } + if (state == NfcAdapter.STATE_ON) { + enableReaderMode(); + } + } else { + if (mEnableNfcDialog == null || !mEnableNfcDialog.isVisible()) { + ShowNFCEnableDialog(); + } + } + } + } + }; + + @TargetApi(Build.VERSION_CODES.KITKAT) + private void enableReaderMode() { + Bundle options = new Bundle(); + if (broadcomWorkaround) { + /* This is a work around for some Broadcom chipsets that does + * the presence check by sending commands that interrupt the + * processing of the ongoing command. + */ + options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, DELAY_PRESENCE); + } + mNfcAdapter.enableReaderMode(mActivity, mReaderCallback, READER_FLAGS, options); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private void disableReaderMode() { + if (mNfcAdapter != null) { + mNfcAdapter.disableReaderMode(mActivity); + } + } + + private static final int REQUEST_NFC_PERMISSIONS = 1; + private static String[] PERMISSIONS_NFC = { + Manifest.permission.NFC + }; + + //Checks if the app has NFC permission + //If the app does not has permission then the user will be prompted to grant permissions + public static void verifyPermissions(Activity activity) { + // Check if we have write permission + int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.NFC); + + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + ActivityCompat.requestPermissions( + activity, + PERMISSIONS_NFC, + REQUEST_NFC_PERMISSIONS + ); + } + } +} + diff --git a/app/src/main/java/com/tangem/cardReader/PBKDF2.java b/app/src/main/java/com/tangem/cardReader/PBKDF2.java new file mode 100644 index 0000000000..0db1ddb69d --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/PBKDF2.java @@ -0,0 +1,103 @@ + +package com.tangem.cardReader; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.macs.HMac; +import org.spongycastle.crypto.params.KeyParameter; + +import java.security.InvalidKeyException; +import java.util.Arrays; + +public final class PBKDF2 { + private static final HMac F =new HMac(new SHA256Digest()); + + /** + * Derive a key. + * + * @param password The password to derive the key from. + * @param iterations The iteration count. + * @return Returns a key derived with the specified parameters. + * @throws InvalidKeyException If the specified length for the derived key + * is to long. + */ + public static byte[] deriveKey(final byte[] password, final byte[] salt, final int iterations) throws InvalidKeyException { + return deriveKey(password, salt, iterations, F.getMacSize()); + } + + /** + * Derive a key with a specified length. + * + * @param password The password to derive the key from. + * @param iterations The iteration count. + * @param len The length of the derived key. + * @return Returns a key derived with the specified parameters. + * @throws InvalidKeyException If the specified length for the derived key + * is to long. + */ + public static byte[] deriveKey(final byte[] password, final byte[] salt, final int iterations, final int len) throws InvalidKeyException { + // Check key length + if (len > ((Math.pow(2, 32) - 1) * F.getMacSize())) + throw new InvalidKeyException("Derived key to long"); + + byte[] derivedKey = new byte[len]; + + final int J = 0; + final int K = F.getMacSize(); + final int U = F.getMacSize() << 1; + final int B = K + U; + final byte[] workingArray = new byte[K + U + 4]; + + // Initialize F + CipherParameters macParams = new KeyParameter(password); + F.init(macParams); + + // Perform iterations + for (int kpos = 0, blk = 1; kpos < len; kpos += K, blk++) { + storeInt32BE(blk, workingArray, B); + + F.update(salt, 0, salt.length); + + F.reset(); + F.update(salt, 0, salt.length); + F.update(workingArray, B, 4); + F.doFinal(workingArray, U); + System.arraycopy(workingArray, U, workingArray, J, K); + + for (int i = 1, j = J, k = K; i < iterations; i++) { + F.init(macParams); + F.update(workingArray, j, K); + F.doFinal(workingArray, k); + + for (int u = U, v = k; u < B; u++, v++) + workingArray[u] ^= workingArray[v]; + + int swp = k; + k = j; + j = swp; + } + + int tocpy = Math.min(len - kpos, K); + System.arraycopy(workingArray, U, derivedKey, kpos, tocpy); + } + + Arrays.fill(workingArray, (byte) 0); + + return derivedKey; + } + + /** + * Convert a 32-bit integer value into a big-endian byte array + * + * @param value The integer value to convert + * @param bytes The byte array to store the converted value + * @param offSet The offset in the output byte array + */ + public static void storeInt32BE(int value, byte[] bytes, int offSet) { + bytes[offSet + 3] = (byte) (value); + bytes[offSet + 2] = (byte) (value >>> 8); + bytes[offSet + 1] = (byte) (value >>> 16); + bytes[offSet] = (byte) (value >>> 24); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/cardReader/ResponseApdu.java b/app/src/main/java/com/tangem/cardReader/ResponseApdu.java new file mode 100644 index 0000000000..2058e4d054 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/ResponseApdu.java @@ -0,0 +1,125 @@ +package com.tangem.cardReader; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; + +public class ResponseApdu { + + private int mSw1 = 0x00; + private int mSw2 = 0x00; + + private byte[] mData = new byte[0]; + private byte[] mBytes = new byte[0]; + + private TLVList tlvList = new TLVList(); + + private String parseError = null; + + private ResponseApdu() { + } + + ResponseApdu(byte[] respApdu) { + if (respApdu.length < 2) { + return; + } + if (respApdu.length > 2) { + mData = new byte[respApdu.length - 2]; + System.arraycopy(respApdu, 0, mData, 0, respApdu.length - 2); + + try { + tlvList = TLVList.fromBytes(mData); + } catch (TLVException e) { + parseError = e.getMessage(); + } + + } + mSw1 = 0x00FF & respApdu[respApdu.length - 2]; + mSw2 = 0x00FF & respApdu[respApdu.length - 1]; + mBytes = respApdu; + } + + public static ResponseApdu Decrypt(byte[] data, byte[] key) throws Exception{ + + if( data.length==2 ) + { + ResponseApdu responseApdu = new ResponseApdu(); + responseApdu.mSw1 = ((int) data[0] & 0xFF); + responseApdu.mSw2 = ((int) data[1] & 0xFF); + return responseApdu; + }else if( data.length>=18 ){ + byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2)); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(decryptedData); + byte[] baLength = new byte[2]; + inputStream.read(baLength); + int length = ((int) baLength[0] & 0xFF) * 256 + ((int) baLength[1] & 0xFF); + if (length > decryptedData.length - 4) + throw new Exception("Can't decrypt - data size invalid"); + byte[] baCRC = new byte[2]; + inputStream.read(baCRC); + byte[] answerData = new byte[length]; + inputStream.read(answerData); + byte[] crc = Util.calculateCRC16(answerData); + if (!Arrays.equals(baCRC, crc)) throw new Exception("Can't decrypt - crc invalid"); + + ResponseApdu responseApdu = new ResponseApdu(); + responseApdu.mSw1 = ((int) data[data.length - 2] & 0xFF); + responseApdu.mSw2 = ((int) data[data.length - 1] & 0xFF); + responseApdu.mBytes = data; + responseApdu.mData = answerData; + + try { + responseApdu.tlvList = TLVList.fromBytes(answerData); + } catch (TLVException e) { + responseApdu.parseError = e.getMessage(); + } + + return responseApdu; + }else{ + throw new Exception("Can't decrypt - data size to small"); + } + } + + + public int getSW1() { + return mSw1; + } + + public int getSW2() { + return mSw2; + } + + public int getSW1SW2() { + return (mSw1 << 8) | mSw2; + } + + public byte[] getData() { + return mData; + } + + public TLVList getTLVs() { + return tlvList; + } + + public boolean isParsedWithError() { + return parseError != null; + } + public String getParseErroMessage() { + return parseError; + } + public byte[] toBytes() { + return mBytes; + } + + public boolean isStatus(int sw1sw2) { + if (getSW1SW2() == sw1sw2) { + return true; + } else { + return false; + } + } + + public String getSW1SW2Description() { + return SW.getDescription(getSW1SW2()); + } +} diff --git a/app/src/main/java/com/tangem/cardReader/SW.java b/app/src/main/java/com/tangem/cardReader/SW.java new file mode 100644 index 0000000000..395d6361b9 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/SW.java @@ -0,0 +1,43 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 07.03.2018. + */ + +public class SW { + public static final int PROCESS_COMPLETED = 0x9000; + public static final int INVALID_PARAMS = 0x6A86; + public static final int ERROR_PROCESSING_COMMAND = 0x6286; + public static final int INVALID_STATE = 0x6985; + public static final int PINS_NOT_CHANGED = PROCESS_COMPLETED; + public static final int PIN1_CHANGED = PROCESS_COMPLETED + 0x0001; + public static final int PIN2_CHANGED = PROCESS_COMPLETED + 0x0002; + public static final int PINS_CHANGED = PROCESS_COMPLETED + 0x0003; + public static final int INS_NOT_SUPPORTED = 0x6D00; + public static final int NEED_ENCRYPTION = 0x6982; + public static final int NEED_PAUSE = 0x9789; + + public static String getDescription(int sw) { + switch (sw) { + case ERROR_PROCESSING_COMMAND: + return "SW_ERROR_PROCESSING_COMMAND"; + case INVALID_PARAMS: + return "SW_INVALID_PARAMS"; + case INVALID_STATE: + return "SW_INVALID_STATE"; + case INS_NOT_SUPPORTED: + return "SW_INS_NOT_SUPPORTED"; + case NEED_ENCRYPTION: + return "SW_NEED_ENCRYPTION"; + case PIN1_CHANGED: + return "SW_PIN1_CHANGED"; + case PIN2_CHANGED: + return "SW_PIN2_CHANGED"; + case PINS_CHANGED: + return "SW_PINS_CHANGED"; + case PROCESS_COMPLETED: + return "SW_PROCESS_COMPLETED"; + } + return "???"; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/SettingsMask.java b/app/src/main/java/com/tangem/cardReader/SettingsMask.java new file mode 100644 index 0000000000..973ccc5544 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/SettingsMask.java @@ -0,0 +1,25 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 07.03.2018. + */ + +public class SettingsMask { + public static final int IsReusable = 0x0001; + public static final int UseActivation = 0x0002; + public static final int UseBlock = 0x0008; + + public static final int AllowSwapPIN = 0x0010; + public static final int AllowSwapPIN2 = 0x0020; + public static final int UseCVC = 0x0040; + public static final int ForbidDefaultPIN = 0x0080; + + public static final int UseOneCommandAtTime = 0x0100; + public static final int UseNDEF = 0x0200; + public static final int UseDynamicNDEF = 0x0400; + public static final int SmartSecurityDelay = 0x0800; + + public static final int Protocol_AllowUnencrypted = 0x1000; + public static final int Protocol_AllowStaticEncryption = 0x2000; + +} diff --git a/app/src/main/java/com/tangem/cardReader/TLV.java b/app/src/main/java/com/tangem/cardReader/TLV.java new file mode 100644 index 0000000000..0a6e398f9e --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/TLV.java @@ -0,0 +1,222 @@ +package com.tangem.cardReader; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * Created by dvol on 23.06.2017. + */ + +public class TLV { + public enum Tag { + TAG_Unknown(0x00), + TAG_CardID(0x01), + TAG_Status(0x02), + TAG_CardPublicKey(0x03), + TAG_CardSignature(0x04), + TAG_CurveID(0x05), + TAG_HashAlgID(0x06), + TAG_SigningMethod(0x07), + TAG_MaxSignatures(0x08), + TAG_PauseBeforePIN2(0x09), + TAG_SettingsMask(0x0A), + TAG_CardData(0x0C), + TAG_NDEFData(0x0D), + TAG_CreateWalletAtPersonalize(0x0E), + TAG_Health(0x0F), + + TAG_PIN(0x10), + TAG_PIN2(0x11), + TAG_NewPIN(0x12), + TAG_NewPIN2(0x13), + TAG_NewPIN_Hash(0x14), + TAG_NewPIN2_Hash(0x15), + TAG_Challenge(0x16), + TAG_Salt(0x17), + TAG_ValidationCounter(0x18), + TAG_CVC(0x19), + + TAG_Session_Key_A(0x1A), + TAG_Session_Key_B(0x1B), + TAG_Pause(0x1C), + + TAG_Manufacture_ID(0x20), + TAG_Manufacturer_Signature(0x21), + + TAG_Issuer_Data_PublicKey(0x30), + TAG_Issuer_Transaction_PublicKey(0x31), + TAG_Issuer_Data(0x32), + TAG_Issuer_Data_Signature(0x33), + TAG_Issuer_Transaction_Signature(0x34), + + TAG_IsActivated(0x3A), + TAG_ActivationSeed(0x3B), + TAG_ResetPIN(0x36), + + TAG_CodePageAddress(0x40), + TAG_CodePageCount(0x41), + TAG_CodeHash(0x42), + + TAG_TrOut_Hash(0x50), + TAG_TrOut_HashSize(0x51), + TAG_TrOut_Raw(0x52), + + TAG_Wallet_PublicKey(0x60), + TAG_Signature(0x61), + TAG_RemainingSignatures(0x62), + TAG_SignedHashes(0x63), + + TAG_Wallet_PrivateKey(0x70), + TAG_Card_PrivateKey(0x71), + TAG_Block_Reason(0x72), + + + TAG_Firmware(0x80), + TAG_Batch(0x81), + TAG_ManufactureDateTime(0x82), + TAG_Issuer_ID(0x83), + TAG_Blockchain_ID(0x84), + TAG_Manufacturer_PublicKey(0x85), + TAG_CardID_Manufacturer_Signature(0x86), + + TAG_Token_Symbol(0xA0), + TAG_Token_Contract_Address(0xA1), + TAG_Token_Decimal(0xA2), + TAG_Denomination(/*0xC0*/0xee), //TODO: quick fix + TAG_ValidatedBalance(0xC1), + TAG_LastSign_Date(0xC2); + + + Tag(int Code) { + this.Code = Code; + } + + public int getCode() { + return Code; + } + + private int Code; + + public static Tag ByCode(int Code) { + Tag[] allTags = Tag.values(); + for (Tag t : allTags) if (t.getCode() == Code) return t; + return TAG_Unknown; + } + } + + private Tag tag; + + public Tag getTag() { + return tag; + } + + public byte[] Value; + + public TLV(Tag tag, byte[] value) { + this.tag = tag; + this.Value = value; + } + + public void WriteToStream(ByteArrayOutputStream stream) throws IOException { + stream.write(tag.getCode()); + if (Value != null) { + if (Value.length > 0xFE) { + stream.write(0xFF); + stream.write((Value.length >> 8) & 0xFF); + stream.write(Value.length & 0xFF); + } else { + stream.write(Value.length & 0xFF); + } + stream.write(Value); + } else { + stream.write(0x00); + } + } + + public static TLV ReadFromStream(ByteArrayInputStream stream) throws IOException { + int code = stream.read(); + if (code == -1) return null; + int len = stream.read(); + if (len == -1) + throw new IOException("Can't read TLV"); + if (len == 0xFF) { + int lenH = stream.read(); + if (lenH == -1) + throw new IOException("Can't read TLV"); + len = stream.read(); + if (len == -1) + throw new IOException("Can't read TLV"); + len |= (lenH << 8); + } + byte[] value = new byte[len]; + if (len > 0) { + if (len != stream.read(value)) { + throw new IOException("Can't read TLV"); + } + } + Tag tag = Tag.ByCode(code); + TLV result = new TLV(tag, value); + return result; + } + + public int getAsInt() { + return Util.byteArrayToInt(Value); + } + + public String getAsHexString() { + return Util.bytesToHex(Value); + } + + public String getAsString() { + //String s=String.valueOf(Value); + if (Value[Value.length - 1] == 0) { + String s1 = new String(Arrays.copyOfRange(Value, 0, Value.length - 1), Charset.forName("utf-8")); + return s1.trim(); + } else { + String s1 = new String(Value, Charset.forName("utf-8")); + return s1.trim(); + + } + } + + @Override + public String toString() { + switch (tag) { + case TAG_CardData: + case TAG_Issuer_Data: { + try { + TLVList tlvSub = TLVList.fromBytes(Value); + return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), tlvSub.toString()); + } catch (TLVException e) { + e.printStackTrace(); + } + if (Value != null) { + return String.format("%s[%d]: %s (non TLV)", tag.name(), Value.length, Util.bytesToHex(Value)); + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + } + case TAG_CurveID: + case TAG_HashAlgID: + case TAG_Blockchain_ID: + case TAG_Manufacture_ID: + case TAG_Firmware: + case TAG_Issuer_ID: + case TAG_Token_Symbol: + if (Value != null) { + return String.format("%s[%d]: %s(%s)", tag.name(), Value.length, Util.bytesToHex(Value), getAsString()); + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + default: + if (Value != null) { + return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value)); + } else { + return String.format("%s[]: [[NULL]]", tag.name()); + } + } + } +} diff --git a/app/src/main/java/com/tangem/cardReader/TLVException.java b/app/src/main/java/com/tangem/cardReader/TLVException.java new file mode 100644 index 0000000000..a213dd71aa --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/TLVException.java @@ -0,0 +1,18 @@ +package com.tangem.cardReader; + +public class TLVException extends Exception { + + private static final long serialVersionUID = 1L; + + public TLVException(String message){ + super(message); + } + + public TLVException(String message, Throwable cause) { + super(message, cause); + } + + public TLVException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/cardReader/TLVList.java b/app/src/main/java/com/tangem/cardReader/TLVList.java new file mode 100644 index 0000000000..59ba0fdc2f --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/TLVList.java @@ -0,0 +1,72 @@ +package com.tangem.cardReader; + +/** + * Created by dvol on 23.06.2017. + */ + +import android.support.annotation.NonNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +public class TLVList extends ArrayList { + public String getParsedTLVs(String Prefix) { + String parsed = ""; + for (int i = 0; i < size(); i++) { + parsed += Prefix + this.get(i).toString() + (i < size() - 1 ? "\n" : ""); + } + return parsed;//.substring(0,parsed.length()-2); + } + + public TLVList() { + super(); + } + + public TLVList(@NonNull Collection c) { + super(c); + } + + public TLV getTLV(TLV.Tag tag) { + for (TLV tlv : this) { + if (tlv.getTag() == tag) return tlv; + } + return null; + } + + public int getTagAsInt(TLV.Tag tag) { + TLV tlv = getTLV(tag); + return Util.byteArrayToInt(tlv.Value); + } + + public byte[] toBytes() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + for (TLV tlv : this) { + try { + tlv.WriteToStream(stream); + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + return stream.toByteArray(); + } + + public static TLVList fromBytes(byte[] mData) throws TLVException { + TLVList tlvList = new TLVList(); + ByteArrayInputStream stream = new ByteArrayInputStream(mData); + TLV tlv = null; + do { + try { + tlv = TLV.ReadFromStream(stream); + if (tlv != null) tlvList.add(tlv); + } catch (IOException e) { + throw new TLVException("TLVError: " + e.getMessage()); + } + } + while (tlv != null); + return tlvList; + } +} diff --git a/app/src/main/java/com/tangem/cardReader/Util.java b/app/src/main/java/com/tangem/cardReader/Util.java new file mode 100644 index 0000000000..79a0205746 --- /dev/null +++ b/app/src/main/java/com/tangem/cardReader/Util.java @@ -0,0 +1,959 @@ +package com.tangem.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.DateFormat; +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> 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); + } +} diff --git a/app/src/main/java/com/tangem/wallet/BTCUtils.java b/app/src/main/java/com/tangem/wallet/BTCUtils.java new file mode 100644 index 0000000000..1f7986049a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BTCUtils.java @@ -0,0 +1,519 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +import android.util.Log; + +import com.tangem.cardReader.Util; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.DERSequenceGenerator; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.text.Format; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Stack; +import java.util.regex.Pattern; + +import static org.bitcoinj.core.ECKey.CURVE; +import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER; + +@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"}) +public final class BTCUtils { + static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N + public static final long MIN_FEE_PER_KB = 10000; + public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1"); + public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000; + public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L; + public static final int MAX_TX_LEN_FOR_NO_FEE = 10000; + public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day) + + public static long calcMinimumFee(int txLen, Collection 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 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 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 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 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 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 getPrevTX(String hex) throws BitcoinException { + byte[] rawTxByte = fromHex(hex); + Transaction baseTx = new Transaction(rawTxByte); + ArrayList prevHashes = new ArrayList(); + 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 getOutputs(List rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException { + ArrayList unspentOutputs = new ArrayList<>(); + + for(Tangem_Card.UnspentTransaction current: rawTxList) + { + byte[] rawTxByte = BTCUtils.fromHex(current.Raw); + if (rawTxByte == null) + { + continue; + } + + Transaction baseTx = new Transaction(rawTxByte); + + if(baseTx.inputs.length == 0 || baseTx.outputs.length == 0) + throw new IllegalArgumentException("Unable to decode given transaction"); + + byte[] txHash = BTCUtils.reverse(CryptoUtil.doubleSha256(rawTxByte)); + String txHashForBuild = current.txID; + byte[] sign = null; + + for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) { + Transaction.Output output = baseTx.outputs[outputIndex]; + + // find outputs + if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) { + unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign)); + } + } + + } + + return unspentOutputs; + } + + public static byte[] fromHex(String s) { + if (s != null) { + try { + StringBuilder sb = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (!Character.isWhitespace(ch)) { + sb.append(ch); + } + } + s = sb.toString(); + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + int hi = (Character.digit(s.charAt(i), 16) << 4); + int low = Character.digit(s.charAt(i + 1), 16); + if (hi >= 256 || low < 0 || low >= 16) { + return null; + } + data[i / 2] = (byte) (hi | low); + } + return data; + } catch (Exception ignored) { + } + } + return null; + } + + public static byte[] reverse(byte[] bytes) { + byte[] result = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + result[i] = bytes[bytes.length - i - 1]; + } + return result; + } + + public static byte[] reverseInPlace(byte[] bytes) { + int len = bytes.length / 2; + for (int i = 0; i < len; i++) { + byte t = bytes[i]; + bytes[i] = bytes[bytes.length - i - 1]; + bytes[bytes.length - i - 1] = t; + } + return bytes; + } + + public static int findSpendableOutput(Transaction tx, String forAddress, long minAmount) throws BitcoinException { + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(forAddress).bytes; + int indexOfOutputToSpend = -1; + for (int indexOfOutput = 0; indexOfOutput < tx.outputs.length; indexOfOutput++) { + Transaction.Output output = tx.outputs[indexOfOutput]; + if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) { + indexOfOutputToSpend = indexOfOutput; + break;//only one input is supported for now + } + } + if (indexOfOutputToSpend == -1) { + throw new BitcoinException(BitcoinException.ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS, "No spendable standard outputs for " + forAddress + " have found", forAddress); + } + final long spendableOutputValue = tx.outputs[indexOfOutputToSpend].value; + if (spendableOutputValue < minAmount) { + throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Unspent amount is too small: " + spendableOutputValue, spendableOutputValue); + } + return indexOfOutputToSpend; + } + + public static void verify(Transaction.Script[] scripts, Transaction spendTx) throws Transaction.Script.ScriptInvalidException { + for (int i = 0; i < scripts.length; i++) { + Stack 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 outputsToSpend; + + public FeeChangeAndSelectedOutputs(long fee, long change, long amountForRecipient, ArrayList outputsToSpend) { + this.fee = fee; + this.change = change; + this.amountForRecipient = amountForRecipient; + this.outputsToSpend = outputsToSpend; + } + } + + public static FeeChangeAndSelectedOutputs calcFeeChangeAndSelectOutputsToSpend(List unspentOutputs, long amountToSend, long extraFee, final boolean isPublicKeyCompressed) throws BitcoinException { + long fee = 0;//calculated below + long change = 0; + long valueOfUnspentOutputs; + ArrayList 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); + + } +} diff --git a/app/src/main/java/com/tangem/wallet/Base58.java b/app/src/main/java/com/tangem/wallet/Base58.java new file mode 100644 index 0000000000..0948e420fc --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Base58.java @@ -0,0 +1,115 @@ +package com.tangem.wallet; + +import java.math.BigInteger; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class Base58 { + private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + + private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long + private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS + private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, + -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, + -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + + public static byte[] decodeBase58(String input) { + if (input == null) { + return null; + } + input = input.trim(); + if (input.length() == 0) { + return new byte[0]; + } + BigInteger resultNum = BigInteger.ZERO; + int nLeadingZeros = 0; + while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) { + nLeadingZeros++; + } + long acc = 0; + int nDigits = 0; + int p = nLeadingZeros; + while (p < input.length()) { + int v = BASE58_VALUES[input.charAt(p) & 0xff]; + if (v >= 0) { + acc *= 58; + acc += v; + nDigits++; + if (nDigits == BASE58_CHUNK_DIGITS) { + resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc)); + acc = 0; + nDigits = 0; + } + p++; + } else { + break; + } + } + if (nDigits > 0) { + long mul = 58; + while (--nDigits > 0) { + mul *= 58; + } + resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc)); + } + final int BASE58_SPACE = -2; + while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) { + p++; + } + if (p < input.length()) { + return null; + } + byte[] plainNumber = resultNum.toByteArray(); + int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0; + byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs]; + System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs); + return result; + } + + public static String encodeBase58(byte[] input) { + if (input == null) { + return null; + } + StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1); + BigInteger bn = new BigInteger(1, input); + long rem; + while (true) { + BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD); + bn = divideAndRemainder[0]; + rem = divideAndRemainder[1].longValue(); + if (bn.compareTo(BigInteger.ZERO) == 0) { + break; + } + for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) { + str.append(BASE58[(int) (rem % 58)]); + rem /= 58; + } + } + while (rem != 0) { + str.append(BASE58[(int) (rem % 58)]); + rem /= 58; + } + str.reverse(); + int nLeadingZeros = 0; + while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) { + str.insert(0, BASE58[0]); + nLeadingZeros++; + } + return str.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/BitcoinException.java b/app/src/main/java/com/tangem/wallet/BitcoinException.java new file mode 100644 index 0000000000..4051775e60 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BitcoinException.java @@ -0,0 +1,35 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +@SuppressWarnings("WeakerAccess") +public final class BitcoinException extends Exception { + public static final int ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS = 0; + public static final int ERR_INSUFFICIENT_FUNDS = 1; + public static final int ERR_WRONG_TYPE = 2; + public static final int ERR_BAD_FORMAT = 3; + public static final int ERR_INCORRECT_PASSWORD = 4; + public static final int ERR_MEANINGLESS_OPERATION = 5; + public static final int ERR_NO_INPUT = 6; + public static final int ERR_FEE_IS_TOO_BIG = 7; + public static final int ERR_FEE_IS_LESS_THEN_ZERO = 8; + public static final int ERR_CHANGE_IS_LESS_THEN_ZERO = 9; + public static final int ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO = 10; + public static final int ERR_UNSUPPORTED = 11; + + public final int errorCode; + @SuppressWarnings({"WeakerAccess", "unused"}) + public final Object extraInformation; + + public BitcoinException(int errorCode, String detailMessage, Object extraInformation) { + super(detailMessage); + this.errorCode = errorCode; + this.extraInformation = extraInformation; + } + + public BitcoinException(int errorCode, String detailMessage) { + this(errorCode, detailMessage, null); + } +} diff --git a/app/src/main/java/com/tangem/wallet/BitcoinInputStream.java b/app/src/main/java/com/tangem/wallet/BitcoinInputStream.java new file mode 100644 index 0000000000..fa0ed57c0d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BitcoinInputStream.java @@ -0,0 +1,69 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; + +@SuppressWarnings("WeakerAccess") +public class BitcoinInputStream extends ByteArrayInputStream { + public BitcoinInputStream(byte[] buf) { + super(buf); + } + + @SuppressWarnings("unused") + public BitcoinInputStream(byte[] buf, int offset, int length) { + super(buf, offset, length); + } + + public int readInt16() throws EOFException { + return (readByte() & 0xff) | ((readByte() & 0xff) << 8); + } + + public int readInt32() throws EOFException { + return (readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 24); + } + + public long readInt64() throws EOFException { + return (readInt32() & 0xFFFFFFFFL )| ((readInt32() & 0xFFFFFFFFL) << 32); + } + + public int readByte() throws EOFException { + int readedByte = super.read(); + if (readedByte == -1) { + throw new EOFException(); + } + return readedByte; + } + + public long readVarInt() throws EOFException { + int readedByte = readByte(); + if (readedByte < 0xfd) { + return readedByte; + } else if (readedByte == 0xfd) { + return readInt16(); + } else if (readedByte == 0xfe) { + return readInt32(); + } else { + return readInt64(); + } + } + + public byte[] readChars(final int count) throws IOException { + byte[] buf = new byte[count]; + int off = 0; + while (off != count) { + int bytesReadCurr = read(buf, off, count - off); + if (bytesReadCurr == -1) { + throw new EOFException(); + } else { + off += bytesReadCurr; + } + } + return buf; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java b/app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java new file mode 100644 index 0000000000..7963eee0d5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BitcoinOutputStream.java @@ -0,0 +1,42 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ +import java.io.ByteArrayOutputStream; + +@SuppressWarnings("WeakerAccess") +public final class BitcoinOutputStream extends ByteArrayOutputStream { + + public void writeInt16(int value) { + write(value & 0xff); + write((value >> 8) & 0xff); + } + + public void writeInt32(int value) { + write(value & 0xff); + write((value >> 8) & 0xff); + write((value >> 16) & 0xff); + write((value >>> 24) & 0xff); + } + + public void writeInt64(long value) { + writeInt32((int) (value & 0xFFFFFFFFL)); + writeInt32((int) ((value >>> 32) & 0xFFFFFFFFL)); + } + + public void writeVarInt(long value) { + if (value < 0xfd) { + write((int) (value & 0xff)); + } else if (value < 0xffff) { + write(0xfd); + writeInt16((int) value); + } else if (value < 0xffffffffL) { + write(0xfe); + writeInt32((int) value); + } else { + write(0xff); + writeInt64(value); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/Blockchain.java b/app/src/main/java/com/tangem/wallet/Blockchain.java new file mode 100644 index 0000000000..bf214a32a8 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Blockchain.java @@ -0,0 +1,99 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.google.common.base.Strings; +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.Util; + +import org.bitcoinj.core.Base58; + +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Created by dvol on 06.08.2017. + */ +public enum Blockchain { + Unknown("", "", 1.0, R.drawable.ic_logo_small, ""), + Bitcoin("BTC", "BTC", 100000000.0, R.drawable.bitcoins, "Bitcoin"), + BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.bitcoins_testnet, "Bitcoin Testnet"), + Ethereum("ETH", "ETH", 1.0, R.drawable.ethereum, "Ethereum"), + EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ethereum_testnet, "Ethereum Testnet"), + Token("ETH\\XTZ", "BAT", 1.0, R.drawable.bat_token, "Ethereum"), + BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.bitcoin_cash, "Bitcoin Cash"), + BitcoinCashTestNet("BCH/test", "BTC", 100000000.0, R.drawable.bitcoin_cash, "Bitcoin Cash Testnet"); + + + Blockchain(String ID, String Currency, double Multiplier, int ImageResource, String officialName) { + mID = ID; + mCurrency = Currency; + mMultiplier = Multiplier; + mImageResource = ImageResource; + mOfficialName = officialName; + } + + private String mID, mOfficialName; + private double mMultiplier; + private String mCurrency; + private int mImageResource; + + public String getID() { + return mID; + } + + public String getOfficialName() { + return mOfficialName; + } + + public double getMultiplier() { + return mMultiplier; + } + + public String getCurrency() { + return mCurrency; + } + + public static Blockchain fromId(String id) { + for (Blockchain blockchain : values()) { + if (blockchain.getID().equals(id)) return blockchain; + } + return null; + } + + public static Blockchain fromCurrency(String currency) { + for (Blockchain blockchain : values()) { + if (blockchain.getCurrency() == currency) return blockchain; + } + return null; + } + + public static String[] getCurrencies() { + String[] result = new String[values().length - 1]; + for (int i = 0; i < result.length - 1; i++) { + result[i] = values()[i + 1].getCurrency(); + } + return result; + } + + public int getImageResource() { + return mImageResource; + } + + public int getImageResource(android.content.Context context, String name) { + if(Strings.isNullOrEmpty(name)) + return getImageResource(); + + name = name.toLowerCase(); + + int resourceId = context.getResources().getIdentifier(name+"_token", "drawable", context.getPackageName()); + + if(resourceId <= 0) + return R.drawable.ethereum; + return resourceId; + } +} diff --git a/app/src/main/java/com/tangem/wallet/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/BtcCashEngine.java new file mode 100644 index 0000000000..076d7b2905 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BtcCashEngine.java @@ -0,0 +1,428 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; +import com.tangem.cardReader.Util; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class BtcCashEngine extends CoinEngine{ + public String GetNextNode(Tangem_Card mCard) + { + + return "35.157.238.5"; + } + public int GetNextNodePort(Tangem_Card mCard) + { + + return 51001; + } + public String GetNode(Tangem_Card mCard) + { + return "35.157.238.5"; + } + public int GetNodePort(Tangem_Card mCard) + { + return 51001; + } + + public void SwitchNode(Tangem_Card mCard) + { + } + + public boolean InOutPutVisible() + { + return true; + } + + public boolean AwaitingConfirmation(Tangem_Card card) + { + return card.getBalanceUnconfirmed()!=0; + } + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + return GetBalance(mCard); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + return true; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + return card.getBalance() > 0; + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); + + // Convert Balance to BigDecimal + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + maxValue = maxValue.divide(new BigDecimal(1000)); + + //if (use_mCurrency) { + amountValue = amountValue.divide(new BigDecimal(1000)); + //} + + if (amountValue.compareTo(maxValue) > 0) { + return false; + } + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + return card.hasBalanceInfo(); + } + + public String GetBalanceCurrency(Tangem_Card card) + { + return "mBCH"; + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return mCard.getUnspentTransactions().size() != 0; + } + + public String GetFeeCurrency() + { + return "mBCH"; + } + + public boolean ValdateAddress(String address, Tangem_Card card){ + if(address == null || address.isEmpty()) + { + return false; + } + + if(address.length() < 25) + { + return false; + } + + if(address.length() > 35) + { + return false; + } + + if(!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) + { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if(decAddress == null || decAddress.length == 0) + { + return false; + } + + byte[] rip = new byte[21]; + for(int i =0; i < 21; ++i) + { + rip[i] = decAddress[i]; + } + + byte[] kcv = CryptoUtil.doubleSha256(rip); + + for(int i =0; i < 4; ++i) + { + if(kcv[i] != decAddress[21+i]) + return false; + } + + if(card.getBlockchain()!=Blockchain.BitcoinCashTestNet && card.getBlockchain()!=Blockchain.BitcoinCash) + { + return false; + } + + if(card.getBlockchain()==Blockchain.BitcoinCashTestNet && (address.startsWith("1") || address.startsWith("3"))) + { + return false; + } + + return true; + } + + + public int GetTokenDecimals(Tangem_Card card) + { + return 0; + } + + public String GetContractAddress(Tangem_Card card) + { + return ""; + } + + public boolean IsNeedCheckNode() + { + return true; + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + return Uri.parse((mCard.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + mCard.getWallet()); + } + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("bitcoincash:" + mCard.getWallet()); + } + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + Long amount = null; + try { + amount = mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount ==0) + return false; + + if(fee > amount) + return false; + + if(fee < minFeeInInternalUnits) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + return GetAmountEqualentDescriptor(mCard, fee); + } + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + Double balance = Double.NaN; + try{ + Long val = mCard.getBalance(); + balance = mCard.AmountFromInternalUnits(val); + } + catch(Exception ex) + { + mCard.setRate(0); + } + + return mCard.getAmountEquivalentDescription(balance); + } + + public String GetBalance(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.AmountFromInternalUnits(mCard.getBalance()); + return mCard.getAmountDescription(balance); + } else { + return "-- -- -- " + mCard.getBlockchain().getCurrency(); + } + } + + public String GetBalanceValue(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier() / 1000.0); + + String output = FormatUtil.DoubleToString(balance); + //String pattern = "#0.000"; // If you like 4 zeros + //DecimalFormat myFormatter = new DecimalFormat(pattern); + //String output = myFormatter.format(balance); + return output; + + //return Double.toString(balance); + } + else + { + return "0"; + } + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + + byte netSelectionByte; + switch (mCard.getBlockchain()) { + case BitcoinCash: + netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet + break; + default : + netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet + break; + } + + byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash2[] = Util.calculateRIPEMD160(hash1); + + ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); + + BB.put(netSelectionByte); + BB.put(hash2); + + byte hash3[] = Util.calculateSHA256(BB.array()); + byte hash4[] = Util.calculateSHA256(hash3); + + BB = ByteBuffer.allocate(hash2.length + 5); + BB.put(netSelectionByte); //BB.put((byte) 0x6f); + BB.put(hash2); + BB.put(hash4[0]); + BB.put(hash4[1]); + BB.put(hash4[2]); + BB.put(hash4[3]); + + return org.bitcoinj.core.Base58.encode(BB.array()); + + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + byte[] reversed=new byte[bytes.length]; + for(int i=0; i 0) { + return String.format("≈ USD %.2f", amount * rate); + } else { + return "≈ USD  ---"; + } + } + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + return getAmountEquivalentDescriptionBTC(Double.parseDouble(value)/1000.0, mCard.getRate()); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + String myAddress = mCard.getWallet(); + byte[] pbKey = mCard.getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY + String outputAddress = toValue; + String changeAddress = myAddress; + + // Build script for our address + List rawTxList = mCard.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + + long fees = FormatUtil.ConvertStringToLong(feeValue); + long amount = FormatUtil.ConvertStringToLong(amountValue); + amount = amount - fees; + + long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] dataForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change); + + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); + + unspentOutputs.get(i).bodyDoubleHash = doubleHashData; + unspentOutputs.get(i).bodyHash = hashData; + + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + dataForSign[i] = newTX; + } + else + { + dataForSign[i] = doubleHashData; + } + + } + + byte[] signFromCard = null; + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < dataForSign.length; i++) { + if (i != 0 && dataForSign[0].length != dataForSign[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(dataForSign[i]); + } + signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value; + } + else { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + s = CryptoUtil.toCanonicalised(s); + byte[] encodingSign = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey); + + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/BtcEngine.java b/app/src/main/java/com/tangem/wallet/BtcEngine.java new file mode 100644 index 0000000000..963ff746d9 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/BtcEngine.java @@ -0,0 +1,564 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; +import com.tangem.cardReader.Util; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Random; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class BtcEngine extends CoinEngine{ + public String GetNextNode(Tangem_Card mCard) + { + return getNextServiceHost(mCard); + } + public int GetNextNodePort(Tangem_Card mCard) + { + return getNextServicePort(mCard); + } + public String GetNode(Tangem_Card mCard) + { + return getServiceHost(mCard); + } + public int GetNodePort(Tangem_Card mCard) + { + return getServicePort(mCard); + } + + public void SwitchNode(Tangem_Card mCard) + { + SelectNextBitconServiceIndex(); + } + + public static String[] GetBitcoinServiceHosts() { + return new String[]{"vps.hsmiths.com", "tardis.bauerj.eu" /*"arihancckjge66iv.onion"*/, "electrumx.bot.nu","electrumx.hopto.org"/* "btc.asis.io"*/, "e-x.not.fyi", "electrum.backplanedns.org", "helicarrier.bauerj.eu", "electrum.vom-stausee.de", "electrum0.snel.it", "kirsche.emzy.de"}; + } + + public static String[] GetBitcoinTestNetServiceHosts() { + return new String[]{/*"testnetnode.arihanc.com"*/"testnet.hsmiths.com", "testnet.qtornado.com", "testnet1.bauerj.eu"}; + } + + public static Integer[] GetBitcoinServicePorts() { + return new Integer[]{8080,50001/* 8080*/, 50001, 50001, 50001, 50001, 50001, 50001, 50001, 50001}; + } + + public static Integer[] GetBitcoinTestNetServicePorts() { + return new Integer[]{/*51001*/53011, 51001, 50001}; + } + + static int serviceIndex = GetNextBitconMainNetServiceIndex(); + + static int dynamicIndex = GetNextBitconMainNetServiceIndex(); + + static int serviceIndexTestNet = GetNextBitconTestNetServiceIndex(); + + static int dynamicTestNetIndex = GetNextBitconTestNetServiceIndex(); + + static long lastChangeServiceIndex = 0; + + public static int GetNextBitconMainNetServiceIndex() { + Random r = new Random(); + return r.nextInt(GetBitcoinServiceHosts().length); + } + + public static void SelectNextBitconMainNetServiceIndex() { + //serviceIndex = GetNextBitconMainNetServiceIndex(); + serviceIndex++; + if (serviceIndex > GetBitcoinServiceHosts().length - 1) serviceIndex = 0; + } + + public static int GetNextBitconTestNetServiceIndex() { + Random r = new Random(); + return r.nextInt(GetBitcoinTestNetServiceHosts().length); + } + + public static void SelectNextBitconTestNetServiceIndex() { + //serviceIndexTestNet = GetNextBitconTestNetServiceIndex(); + + serviceIndexTestNet++; + if (serviceIndexTestNet > GetBitcoinTestNetServiceHosts().length - 1) serviceIndexTestNet = 0; + } + + public static void setNextDynamicIndex() { + //dynamicIndex = GetNextBitconMainNetServiceIndex(); + dynamicIndex++; + if (dynamicIndex > GetBitcoinServiceHosts().length - 1) dynamicIndex = 0; + } + + public static void setNextDynamicTestNet() { + //dynamicTestNetIndex = GetNextBitconTestNetServiceIndex(); + dynamicTestNetIndex++; + if (dynamicTestNetIndex > GetBitcoinTestNetServiceHosts().length - 1) dynamicTestNetIndex = 0; + + } + + public static void SelectNextBitconServiceIndex() { + long unixTime = System.currentTimeMillis() / 1000L; + long nextStampOffset = 5; + if(lastChangeServiceIndex == 0) + { + lastChangeServiceIndex = unixTime; + } + else if(lastChangeServiceIndex + nextStampOffset > unixTime ) + { + return; + } + + lastChangeServiceIndex = unixTime; + + SelectNextBitconMainNetServiceIndex(); + SelectNextBitconTestNetServiceIndex(); + } + + public static String getServiceHost(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: + return GetBitcoinServiceHosts()[serviceIndex]; //"hsmiths.changeip.net"; + case BitcoinTestNet: + return GetBitcoinTestNetServiceHosts()[serviceIndexTestNet]; //"testnetnode.arihanc.com"; + } + return null; + } + + public static String getNextServiceHost(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: { + setNextDynamicIndex(); + return GetBitcoinServiceHosts()[dynamicIndex]; + } + case BitcoinTestNet: { + setNextDynamicTestNet(); + return GetBitcoinTestNetServiceHosts()[dynamicTestNetIndex]; //"testnetnode.arihanc.com"; + } + //case BitcoinCash: + //{ + // return GetBitcoinCashServiceHosts()[0]; + //} + //case BitcoinCashTestNet: { + // return GetBitcoinCashTestNetServiceHosts()[0]; + //} + } + return null; + } + + public static int getNextServicePort(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: { + setNextDynamicIndex(); + return GetBitcoinServicePorts()[dynamicIndex];//8080; + } + case BitcoinTestNet: { + setNextDynamicTestNet(); + return GetBitcoinTestNetServicePorts()[dynamicTestNetIndex];//51001; + } + } + return 8080; + } + + public static int getServicePort(Tangem_Card mCard) { + switch (mCard.getBlockchain()) { + case Bitcoin: + return GetBitcoinServicePorts()[serviceIndex];//8080; + case BitcoinTestNet: + return GetBitcoinTestNetServicePorts()[serviceIndexTestNet];//51001; + } + return 8080; + } + + + public boolean InOutPutVisible() + { + return true; + } + + public boolean AwaitingConfirmation(Tangem_Card card) + { + return card.getBalanceUnconfirmed()!=0; + } + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + return GetBalance(mCard); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + return true; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + return card.getBalance() > 0; + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); + + // Convert Balance to BigDecimal + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + maxValue = maxValue.divide(new BigDecimal(1000)); + + //if (use_mCurrency) { + amountValue = amountValue.divide(new BigDecimal(1000)); + //} + + if (amountValue.compareTo(maxValue) > 0) { + return false; + } + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + return card.hasBalanceInfo(); + } + + public String GetBalanceCurrency(Tangem_Card card) + { + return "mBTC"; + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return mCard.getUnspentTransactions().size() != 0; + } + + public String GetFeeCurrency() + { + return "mBTC"; + } + + public boolean ValdateAddress(String address, Tangem_Card card){ + if(address == null || address.isEmpty()) + { + return false; + } + + if(address.length() < 25) + { + return false; + } + + if(address.length() > 35) + { + return false; + } + + if(!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) + { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if(decAddress == null || decAddress.length == 0) + { + return false; + } + + byte[] rip = new byte[21]; + for(int i =0; i < 21; ++i) + { + rip[i] = decAddress[i]; + } + + byte[] kcv = CryptoUtil.doubleSha256(rip); + + for(int i =0; i < 4; ++i) + { + if(kcv[i] != decAddress[21+i]) + return false; + } + + if(card.getBlockchain()!=Blockchain.BitcoinTestNet && card.getBlockchain()!=Blockchain.Bitcoin) + { + return false; + } + + if(card.getBlockchain()==Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) + { + return false; + } + + return true; + } + + + public int GetTokenDecimals(Tangem_Card card) + { + return 0; + } + + public String GetContractAddress(Tangem_Card card) + { + return ""; + } + + public boolean IsNeedCheckNode() + { + return true; + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + return Uri.parse((mCard.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + mCard.getWallet()); + } + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("bitcoin:" + mCard.getWallet()); + } + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + Long amount = null; + try { + amount = mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount ==0) + return false; + + if(fee > amount) + return false; + + if(fee < minFeeInInternalUnits) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + return GetAmountEqualentDescriptor(mCard, fee); + } + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + Double balance = Double.NaN; + try{ + Long val = mCard.getBalance(); + balance = mCard.AmountFromInternalUnits(val); + } + catch(Exception ex) + { + mCard.setRate(0); + } + + return mCard.getAmountEquivalentDescription(balance); + } + + public String GetBalance(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.AmountFromInternalUnits(mCard.getBalance()); + return mCard.getAmountDescription(balance); + } else { + return "-- -- -- " + mCard.getBlockchain().getCurrency(); + } + } + + public String GetBalanceValue(Tangem_Card mCard) + { + if (mCard.hasBalanceInfo()) { + Double balance = mCard.getBalance() / (mCard.getBlockchain().getMultiplier() / 1000.0); + + String output = FormatUtil.DoubleToString(balance); + //String pattern = "#0.000"; // If you like 4 zeros + //DecimalFormat myFormatter = new DecimalFormat(pattern); + //String output = myFormatter.format(balance); + return output; + + //return Double.toString(balance); + } + else + { + return "0"; + } + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + + byte netSelectionByte; + switch (mCard.getBlockchain()) { + case Bitcoin: + netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet + break; + default : + netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet + break; + } + + byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash2[] = Util.calculateRIPEMD160(hash1); + + ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); + + BB.put(netSelectionByte); + BB.put(hash2); + + byte hash3[] = Util.calculateSHA256(BB.array()); + byte hash4[] = Util.calculateSHA256(hash3); + + BB = ByteBuffer.allocate(hash2.length + 5); + BB.put(netSelectionByte); //BB.put((byte) 0x6f); + BB.put(hash2); + BB.put(hash4[0]); + BB.put(hash4[1]); + BB.put(hash4[2]); + BB.put(hash4[3]); + + return org.bitcoinj.core.Base58.encode(BB.array()); + + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + byte[] reversed=new byte[bytes.length]; + for(int i=0; i 0) { + return String.format("≈ USD %.2f", amount * rate); + } else { + return "≈ USD  ---"; + } + } + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + return getAmountEquivalentDescriptionBTC(Double.parseDouble(value)/1000.0, mCard.getRate()); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + String myAddress = mCard.getWallet(); + byte[] pbKey = mCard.getWalletPublicKey(); + String outputAddress = toValue; + String changeAddress = myAddress; + + // Build script for our address + List rawTxList = mCard.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + + long fees = FormatUtil.ConvertStringToLong(feeValue); + long amount = FormatUtil.ConvertStringToLong(amountValue); + amount = amount - fees; + + long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] dataForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change); + + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); + + unspentOutputs.get(i).bodyDoubleHash = doubleHashData; + unspentOutputs.get(i).bodyHash = hashData; + + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + dataForSign[i] = newTX; + } + else + { + dataForSign[i] = doubleHashData; + } + + } + + byte[] signFromCard = null; + if(mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw || mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Raw_Validated_By_Issuer) + { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < dataForSign.length; i++) { + if (i != 0 && dataForSign[0].length != dataForSign[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(dataForSign[i]); + } + signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value; + } + else { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, mCard.getSigningMethod() == Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer, null, mCard.getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + s = CryptoUtil.toCanonicalised(s); + byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); + + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ByteUtil.java b/app/src/main/java/com/tangem/wallet/ByteUtil.java new file mode 100644 index 0000000000..2d27915fc0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ByteUtil.java @@ -0,0 +1,39 @@ +package com.tangem.wallet; + +public class ByteUtil { + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static byte[] and(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] & b2[i]); + } + return ret; + } + + public static byte[] or(byte[] b1, byte[] b2) { + if (b1.length != b2.length) throw new RuntimeException("Array sizes differ"); + byte[] ret = new byte[b1.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) (b1[i] | b2[i]); + } + return ret; + } + + public static boolean isNullOrZeroArray(byte[] array){ + return (array == null) || (array.length == 0); + } + + public static boolean isSingleZero(byte[] array){ + return (array.length == 1 && array[0] == 0); + } + + public static int length(byte[]... bytes) { + int result = 0; + for (byte[] array : bytes) { + result += (array == null) ? 0 : array.length; + } + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CardInfoActivity.java b/app/src/main/java/com/tangem/wallet/CardInfoActivity.java new file mode 100644 index 0000000000..78f53030f1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CardInfoActivity.java @@ -0,0 +1,122 @@ +package com.tangem.wallet; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; + +public class CardInfoActivity extends AppCompatActivity implements WalletInfoFragment.OnFragmentInteractionListener { + + /** + * The {@link android.support.v4.view.PagerAdapter} that will provide + * fragments for each of the sections. We use a + * {@link FragmentPagerAdapter} derivative, which will keep every + * loaded fragment in memory. If this becomes too memory intensive, it + * may be best to switch to a + * {@link android.support.v4.app.FragmentStatePagerAdapter}. + */ + private SectionsPagerAdapter mSectionsPagerAdapter; + + /** + * The {@link ViewPager} that will host the section contents. + */ + private ViewPager mViewPager; + + + private Tangem_Card mCard; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_card_info); + + // Create the adapter that will return a fragment for each of the three + // primary sections of the activity. + mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + + // Set up the ViewPager with the sections adapter. + mViewPager = (ViewPager) findViewById(R.id.container); + mViewPager.setAdapter(mSectionsPagerAdapter); + + TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); + tabLayout.setupWithViewPager(mViewPager); + + String UID = getIntent().getStringExtra("UID"); + mCard = new Tangem_Card(UID); + mCard.LoadFromBundle(getIntent().getBundleExtra("Card")); + + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_card_info, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + + /** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + if (position == 0) { + return WalletInfoFragment.newInstance(mCard); + } /*else if (position == 1) { + return WalletUnspentFragment.newInstance(mCard); + } else if (position == 2) { + return WalletHistoryFragment.newInstance(mCard); + }*/ + return null; + } + + @Override + public int getCount() { + // Show 3 total pages. + return 3; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "Wallet info"; + case 1: + return "Unspent"; + case 2: + return "History"; + } + return null; + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/CardListAdapter.java b/app/src/main/java/com/tangem/wallet/CardListAdapter.java new file mode 100644 index 0000000000..bc3668bb3c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CardListAdapter.java @@ -0,0 +1,599 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.support.v7.widget.CardView; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.tangem.wallet.Tangem_Card.CountOurTx; + +public class CardListAdapter extends RecyclerView.Adapter { + + + public static class CardViewHolder extends RecyclerView.ViewHolder { + CardView cv; + TextView tvBalance, tvOffline, tvBalanceEquivalent, tvWallet, tvInputs, tvStatusInBlockchain, tvCardID, tvLastInput, lbLastInput, lbLastOutput, tvLastOutput, tvBlockchain; + TextView tvType, tvTypeBg, tvVoid; + ImageView imgBlockchain, imgSecurityNotification; + View llCardLoaded, llCardEmpty, llCardError, llCardPurged; + + CardViewHolder(View itemView) { + super(itemView); + cv = itemView.findViewById(R.id.cvCard); + tvBalance = itemView.findViewById(R.id.tvBalance); + tvOffline = itemView.findViewById(R.id.tvOffline); + tvBalanceEquivalent = itemView.findViewById(R.id.tvBalanceEquivalent); + tvWallet = itemView.findViewById(R.id.tvWallet); + tvCardID = itemView.findViewById(R.id.tvCardID); + + tvType = itemView.findViewById(R.id.tvType); + tvTypeBg = itemView.findViewById(R.id.tvTypeBg); + tvVoid = itemView.findViewById(R.id.tvVoid); + + if (tvCardID != null) { + tvCardID.requestFocus(); + } + + tvInputs = itemView.findViewById(R.id.tvInputs); + + tvLastInput = itemView.findViewById(R.id.tvLastInput); + lbLastInput = itemView.findViewById(R.id.lbLastInput); + + lbLastOutput = itemView.findViewById(R.id.lbLastOutput); + tvLastOutput = itemView.findViewById(R.id.tvLastOutput); + + tvBlockchain = itemView.findViewById(R.id.tvBlockchain); + imgBlockchain = itemView.findViewById(R.id.imgBlockchain); + llCardLoaded = itemView.findViewById(R.id.cardLoaded); + llCardEmpty = itemView.findViewById(R.id.cardEmpty); + llCardError = itemView.findViewById(R.id.cardError); + llCardPurged = itemView.findViewById(R.id.cardPurged); + imgSecurityNotification = itemView.findViewById(R.id.imgSecurityNotification); + } + } + + public interface UiCallbacks { + void onViewCard(Bundle cardInfo); + } + + private LayoutInflater mLayoutInflater; + private Context mContext; + private UiCallbacks mUiCallbacks; + private List mCards = new ArrayList<>(100); + + public CardListAdapter(LayoutInflater layoutInflater, Bundle instate, UiCallbacks uiCallbacks) { + mLayoutInflater = layoutInflater; + mContext = layoutInflater.getContext(); + mUiCallbacks = uiCallbacks; + if (instate != null) { + // restore state + + ArrayList UIDs = instate.getStringArrayList("card_UID"); + + if( UIDs!=null ) { + for (String UID : UIDs) { + Tangem_Card card = new Tangem_Card(UID); + card.LoadFromBundle(instate.getBundle(String.format("card_%s", UID))); + mCards.add(card); + } + } + } + } + + + public void onSaveInstanceState(Bundle outstate) { + ArrayList UIDs = new ArrayList(mCards.size()); + for (Tangem_Card card : mCards) { + Bundle B = new Bundle(); + card.SaveToBundle(B); + outstate.putBundle(String.format("card_%s", card.getUID()), B); + UIDs.add(card.getUID()); + } + outstate.putStringArrayList("card_UID", UIDs); + } + + public void clearCards() { + mCards.clear(); + notifyDataSetChanged(); + } + + public Tangem_Card getCard(int cardIndex) { + return mCards.get(cardIndex); + } + + public Tangem_Card getCardByWallet(String walletAddress) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + return c; + } + } + return null; + } + + public void addCard(Tangem_Card card) { + mCards.add(0,card); + notifyDataSetChanged(); + } + + public void removeCard(int cardIndex) { + mCards.remove(cardIndex); + notifyItemRemoved(cardIndex); + } + + public void removeCard(Tangem_Card card) { + int i; + for (i = 0; i < mCards.size(); i++) { + if (Arrays.equals(mCards.get(i).getCID(), card.getCID())) { + break; + } + } + + if (i == mCards.size()) return; + + removeCard(i); + } + + + public void updateCard(Tangem_Card card) { + int i; + for (i = 0; i < mCards.size(); i++) { + if (Arrays.equals(mCards.get(i).getCID(), card.getCID())) { + break; + } + } + + if (i == mCards.size()) { + mCards.add(card); + } else { + mCards.remove(i); + mCards.add(i, card); + } + notifyDataSetChanged(); + } + + public void UpdateWalletBalance(String walletAddress, Long balanceConfirmed, Long balanceUnconfirmed, String validationNodeDescription) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setBalanceConfirmed(balanceConfirmed); + c.setBalanceUnconfirmed(balanceUnconfirmed); + c.setDecimalBalance(String.valueOf(balanceConfirmed)); + c.setValidationNodeDescription(validationNodeDescription); + notifyDataSetChanged(); + return; + } + } + } + + + public void UpdateWalletCoutConfirmTx(String walletAddress, BigInteger nonce) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.SetConfirmTXCount(nonce); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletBlockchain(String walletAddress, Blockchain blockchain) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setBlockchainID(blockchain.getID()); + notifyDataSetChanged(); + return; + } + } + } + + public void AddWalletBlockchainNameToken(String walletAddress) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.addTokenToBlockchainName(); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletBalance(String walletAddress, Long balanceConfirmed, String balanceString, String validationNodeDescription) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setBalanceConfirmed(balanceConfirmed); + c.setBalanceUnconfirmed(0L); + c.setDecimalBalance(String.valueOf(balanceString)); + c.setValidationNodeDescription(validationNodeDescription); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletBalanceOnlyAlter(String walletAddress, String balanceAlter) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setDecimalBalanceAlter(balanceAlter); + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletUnspent(String walletAddress, JSONArray jsUnspentArray) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + try { + c.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Tangem_Card.UnspentTransaction trUnspent = new Tangem_Card.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + c.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletHistory(String walletAddress, JSONArray jsHistoryArray) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + try { + c.getHistoryTransactions().clear(); + for (int i = 0; i < jsHistoryArray.length(); i++) { + JSONObject jsUnspent = jsHistoryArray.getJSONObject(i); + Tangem_Card.HistoryTransaction trHistory = new Tangem_Card.HistoryTransaction(); + trHistory.txID = jsUnspent.getString("tx_hash"); + trHistory.Height = jsUnspent.getInt("height"); + c.getHistoryTransactions().add(trHistory); + } + } catch (JSONException e) { + e.printStackTrace(); + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletHeader(String walletAddress, JSONObject jsHeader) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + try { + c.getHaedersInfo(); + c.UpdateHeaderInfo(new Tangem_Card.HeaderInfo( + jsHeader.getInt("block_height"), + jsHeader.getInt("timestamp"))); + } catch (JSONException e) { + e.printStackTrace(); + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateRate(String walletAddress, float rate) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setRate(rate); + notifyDataSetChanged(); + break; + } + } + } + + public void UpdateRateAlter(String walletAddress, float rate) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setRateAlter(rate); + notifyDataSetChanged(); + break; + } + } + } + + public void UpdateTransaction(String walletAddress, String txHash, String raw) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + + List listTx = c.getUnspentTransactions(); + for (Tangem_Card.UnspentTransaction tx : listTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + } + } + + List listHTx = c.getHistoryTransactions(); + for (Tangem_Card.HistoryTransaction tx : listHTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + CountOurTx(listHTx); + } + } + notifyDataSetChanged(); + return; + } + } + } + + public void UpdateWalletError(String walletAddress, String error) { + for (Tangem_Card c : mCards) { + if (c.getWallet().equals(walletAddress)) { + c.setError(error); + notifyDataSetChanged(); + return; + } + } + } + + @Override + public int getItemCount() { + return mCards == null ? 0 : mCards.size(); + } + + @Override + public CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_list_item, parent, false); + CardViewHolder cvh = new CardViewHolder(v); + return cvh; + } + + @Override + public void onBindViewHolder(CardViewHolder holder, int position) { + + try { + Tangem_Card card = mCards.get(position); + CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain()); + + int color = android.R.color.black; + + switch (card.getStatus()) { + case NotPersonalized: + color = R.color.card_state_error; + holder.llCardError.setVisibility(View.VISIBLE); + holder.llCardLoaded.setVisibility(View.GONE); + holder.llCardEmpty.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.GONE); + + holder.imgBlockchain.setVisibility(View.INVISIBLE); + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + holder.tvCardID.setVisibility(View.INVISIBLE); + holder.tvCardID.setError(null); + + holder.tvType.setVisibility(View.INVISIBLE); + holder.tvTypeBg.setVisibility(View.INVISIBLE); + holder.tvVoid.setVisibility(View.INVISIBLE); + break; + case Empty: + color = R.color.card_state_empty; + holder.llCardEmpty.setVisibility(View.VISIBLE); + holder.llCardLoaded.setVisibility(View.GONE); + holder.llCardError.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.GONE); + holder.imgBlockchain.setVisibility(View.VISIBLE); + if (card.getBlockchain() != null) { + holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol())); + holder.imgBlockchain.setVisibility(View.VISIBLE); + } else { + holder.imgBlockchain.setVisibility(View.INVISIBLE); + } + if (card.useDefaultPIN1() || card.useDefaultPIN2() || card.getPauseBeforePIN2() > 0 || card.useDevelopersFirmware()) { + //holder.imgSecurityNotification.setVisibility(View.VISIBLE); + } else { + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + } + holder.tvCardID.setText(card.getCIDDescription()); + holder.tvCardID.setError(null); + holder.tvCardID.setVisibility(View.VISIBLE); + + holder.tvType.setVisibility(View.VISIBLE); + holder.tvTypeBg.setVisibility(View.VISIBLE); + if( card.isReusable() ) { + holder.tvType.setText(" REUSABLE REUSABLE "); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_wallet, mContext.getTheme())); + }else{ + holder.tvType.setText(" BANKNOTE BANKNOTE"); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme())); + } + holder.tvVoid.setVisibility(View.INVISIBLE); + break; + case Purged: + color = R.color.card_state_purged; + holder.llCardEmpty.setVisibility(View.GONE); + holder.llCardLoaded.setVisibility(View.GONE); + holder.llCardError.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.VISIBLE); + holder.imgBlockchain.setVisibility(View.VISIBLE); + if (card.getBlockchain() != null) { + holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol())); + holder.imgBlockchain.setVisibility(View.VISIBLE); + } else { + holder.imgBlockchain.setVisibility(View.INVISIBLE); + } + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + holder.tvCardID.setText(card.getCIDDescription()); + holder.tvCardID.setVisibility(View.VISIBLE); + holder.tvCardID.setError(null); + holder.tvType.setVisibility(View.VISIBLE); + holder.tvTypeBg.setVisibility(View.VISIBLE); + holder.tvType.setText(" BANKNOTE BANKNOTE"); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme())); + holder.tvVoid.setVisibility(View.INVISIBLE); + break; + case Loaded: + holder.llCardLoaded.setVisibility(View.VISIBLE); + holder.llCardError.setVisibility(View.GONE); + holder.llCardEmpty.setVisibility(View.GONE); + holder.llCardPurged.setVisibility(View.GONE); + holder.tvCardID.setText(card.getCIDDescription()); + holder.tvCardID.setVisibility(View.VISIBLE); + holder.tvWallet.setText(card.getShortWalletString()); + + holder.tvBlockchain.setText(card.getBlockchainName()); + + if (card.getBlockchain() != null) { + holder.imgBlockchain.setImageResource(card.getBlockchain().getImageResource(this.mContext, card.getTokenSymbol())); + holder.imgBlockchain.setVisibility(View.VISIBLE); + } else { + holder.imgBlockchain.setVisibility(View.INVISIBLE); + } + + if (!engine.HasBalanceInfo(card)) { + color = R.color.card_state_loaded; + } else if (engine.IsBalanceNotZero(card)) { + color = R.color.card_state_loaded_with_coins; + } else { + color = R.color.card_state_loaded_with_zero; + } + + if (holder.tvStatusInBlockchain != null) { + if (card.hasBalanceInfo() && card.hasUnspentInfo() && card.getUnspentTransactions().size() > 0) { + holder.tvStatusInBlockchain.setText("Ok"); + holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.confirmed, mContext.getTheme())); + } else if (!card.hasBalanceInfo() || !card.hasUnspentInfo()) { + holder.tvStatusInBlockchain.setText("-- -- --"); + holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.primary_dark, mContext.getTheme())); + } else { + holder.tvStatusInBlockchain.setText("Not found"); + holder.tvStatusInBlockchain.setTextColor(mContext.getResources().getColor(R.color.not_confirmed, mContext.getTheme())); + } + } + + if ( engine.HasBalanceInfo(card) || card.getOfflineBalance() == null) { + String balance = engine.GetBalance(card); + holder.tvBalance.setText(balance); + holder.tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(card)); + holder.tvBalance.setTextColor(Color.BLACK); + holder.tvOffline.setVisibility(View.INVISIBLE); + + } else { + + String offlineAmount = engine.ConvertByteArrayToAmount(card, card.getOfflineBalance()); + holder.tvBalance.setText(engine.GetAmountDescription(card, offlineAmount)); + holder.tvBalanceEquivalent.setText(engine.GetAmountEqualentDescriptor(card, offlineAmount)); + holder.tvOffline.setVisibility(View.VISIBLE); + } + if (!card.getAmountEquivalentDescriptionAvailable()) { + //holder.tvBalanceEquivalent.setError("Service unavailable"); + } else { + holder.tvBalanceEquivalent.setError(null); + } + + String error = card.getError(); + //holder.tvCardID.setError(error); + + if (holder.tvInputs != null) { + holder.tvInputs.setText(card.getInputsDescription()); + } + + boolean visibleFlag = engine != null ? engine.InOutPutVisible() : true; + int visibleIOPuts = visibleFlag ? View.VISIBLE : View.GONE; + if (holder.tvLastInput != null) { + holder.tvLastInput.setText(card.getLastInputDescription()); + if (card.getLastInputDescription().contains("awaiting")) { + holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.not_confirmed, mContext.getTheme())); + } else if (card.getLastInputDescription().contains("None") || card.getLastInputDescription().contains("--")) { + holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.primary_dark, mContext.getTheme())); + } else { + holder.tvLastInput.setTextColor(mContext.getResources().getColor(R.color.confirmed, mContext.getTheme())); + } + holder.tvLastInput.setVisibility(visibleIOPuts); + } + + if (holder.lbLastInput != null) { + holder.lbLastInput.setVisibility(visibleIOPuts); + } + + if (holder.tvLastOutput != null) { + holder.tvLastOutput.setText(card.getLastOutputDescription()); + holder.tvLastOutput.setVisibility(visibleIOPuts); + } + + if (holder.lbLastOutput != null) { + holder.lbLastOutput.setVisibility(visibleIOPuts); + } + + if (card.useDefaultPIN1() || card.useDefaultPIN2() || card.getPauseBeforePIN2() > 0 || card.useDevelopersFirmware()) { + //holder.imgSecurityNotification.setVisibility(View.VISIBLE); + } else { + holder.imgSecurityNotification.setVisibility(View.INVISIBLE); + } + + holder.tvType.setVisibility(View.VISIBLE); + holder.tvTypeBg.setVisibility(View.VISIBLE); + if( card.isReusable() ) { + holder.tvType.setText(" REUSABLE REUSABLE "); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_wallet, mContext.getTheme())); + holder.tvVoid.setVisibility(View.INVISIBLE); + }else{ + holder.tvType.setText(" BANKNOTE BANKNOTE"); + + if( card.getRemainingSignatures()!=card.getMaxSignatures() ) { + holder.tvVoid.setVisibility(View.VISIBLE); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.msg_err, mContext.getTheme())); + }else{ + holder.tvVoid.setVisibility(View.INVISIBLE); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.type_banknote, mContext.getTheme())); + } + } + + break; + } + + if(card.useDevelopersFirmware()) { + holder.tvType.setText(" DEVELOPER KIT "); + holder.tvTypeBg.setBackgroundColor(mContext.getResources().getColor(R.color.fab, mContext.getTheme())); + holder.tvVoid.setVisibility(View.INVISIBLE); + } + + holder.cv.setOnClickListener(new CardClickListener(position)); + holder.cv.setCardBackgroundColor(mContext.getResources().getColor(color)); + + } catch (Exception e) { + Log.e("onBindViewHolder", e.toString()); + } + } + + private class CardClickListener implements View.OnClickListener { + Tangem_Card card; + int pos; + + CardClickListener(int position) { + card = mCards.get(position); + pos=position; + } + + @Override + public void onClick(View v) { + Bundle b = new Bundle(); + b.putString("UID", card.getUID()); + Bundle bCard = new Bundle(); + card.SaveToBundle(bCard); + b.putBundle("Card", bCard); + mUiCallbacks.onViewCard(b); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java b/app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java new file mode 100644 index 0000000000..b5191ea7d1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CardUnspentListAdapter.java @@ -0,0 +1,83 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.os.Build; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import static android.text.Html.FROM_HTML_MODE_COMPACT; + +/** + * Created by dvol on 17.07.2017. + */ + +public class CardUnspentListAdapter extends BaseAdapter { + private LayoutInflater mLayoutInflater; + private Context mContext; + private Tangem_Card mCard; + + public CardUnspentListAdapter(LayoutInflater layoutInflater, Tangem_Card card) { + mLayoutInflater = layoutInflater; + mContext = layoutInflater.getContext(); + mCard = card; + } + + @Override + public int getCount() { + if (mCard != null && mCard.getUnspentTransactions() != null) + return mCard.getUnspentTransactions().size(); + return 0; + } + + @Override + public Object getItem(int i) { + if (mCard != null && mCard.getUnspentTransactions() != null) + return mCard.getUnspentTransactions().get(i); + return null; + } + + @Override + public long getItemId(int i) { + if (mCard != null && mCard.getUnspentTransactions() != null) + return mCard.getUnspentTransactions().get(i).txID.hashCode(); + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup viewGroup) { + if (convertView == null) { + convertView = mLayoutInflater.inflate(R.layout.card_unspent_list_item, viewGroup, + false); + } + TextView tvItem = (TextView) convertView.findViewById(R.id.tvItem); + + Tangem_Card.UnspentTransaction unspentTransaction = (Tangem_Card.UnspentTransaction) getItem(position); + + String html=String.format("%d mBTC
%s", unspentTransaction.Amount, unspentTransaction.txID); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tvItem.setText(Html.fromHtml(html, FROM_HTML_MODE_COMPACT)); + } else { + tvItem.setText(Html.fromHtml(html.toString())); + } + return convertView; + } + + public void Clear() { + mCard.getUnspentTransactions().clear(); + notifyDataSetChanged(); + } + + public void UpdateUnspent(String tx_hash, int value, int height) { + Tangem_Card.UnspentTransaction newUT=new Tangem_Card.UnspentTransaction(); + newUT.txID=tx_hash; + newUT.Amount=value; + newUT.Height=height; + mCard.getUnspentTransactions().add(newUT); + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java new file mode 100644 index 0000000000..f56c71ab12 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CoinEngine.java @@ -0,0 +1,82 @@ +package com.tangem.wallet; + +import android.net.Uri; + +import com.tangem.cardReader.CardProtocol; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; + +/** + * Created by Ilia on 15.02.2018. + */ + +public abstract class CoinEngine { + + public abstract String GetNextNode(Tangem_Card mCard); + + public abstract int GetNextNodePort(Tangem_Card mCard); + + public abstract String GetNode(Tangem_Card mCard); + + public abstract int GetNodePort(Tangem_Card mCard); + + public abstract void SwitchNode(Tangem_Card mCard); + + public abstract boolean AwaitingConfirmation(Tangem_Card card); + + public abstract boolean HasBalanceInfo(Tangem_Card card); + + public abstract boolean IsBalanceNotZero(Tangem_Card card); + + public abstract boolean IsBalanceAlterNotZero(Tangem_Card card); + + public abstract boolean CheckAmount(Tangem_Card card, String amount) throws Exception; + + public abstract int GetTokenDecimals(Tangem_Card card); + + public abstract String GetContractAddress(Tangem_Card card); + + public abstract byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception; + + public abstract boolean CheckUnspentTransaction(Tangem_Card mCard); + + public abstract Uri getShareWalletURIExplorer(Tangem_Card mCard); + + public abstract Long GetBalanceLong(Tangem_Card mCard); + + public abstract Uri getShareWalletURI(Tangem_Card mCard); + + public abstract String EvaluteFeeEquivalent(Tangem_Card mCard, String fee); + + public abstract boolean CheckAmountValie(Tangem_Card mCard, String amount, String fee, Long minFeeInInternalUnits); + + public abstract boolean InOutPutVisible(); + + public abstract String GetBalance(Tangem_Card mCard); + + public abstract String GetBalanceWithAlter(Tangem_Card mCard); + + public abstract String GetBalanceCurrency(Tangem_Card card); + + public abstract String GetFeeCurrency(); + + public abstract boolean IsNeedCheckNode(); + + public abstract String GetBalanceEquivalent(Tangem_Card mCard); + + public abstract String GetBalanceValue(Tangem_Card mCard); + + public abstract String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception; + + public abstract String GetAmountEqualentDescriptor(Tangem_Card mCard, String value); + + public abstract boolean ValdateAddress(String address, Tangem_Card catd); + + public abstract String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException; + + public abstract String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception; + + public abstract byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception; + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.java b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.java new file mode 100644 index 0000000000..92c91713e9 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.java @@ -0,0 +1,23 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class CoinEngineFactory { + public static CoinEngine Create(Blockchain chain) + { + if(Blockchain.BitcoinCash == chain || Blockchain.BitcoinCashTestNet == chain) { + return new BtcCashEngine(); + }else if(Blockchain.Bitcoin == chain || Blockchain.BitcoinTestNet == chain) { + return new BtcEngine(); //TODO: ВРЕМЕНГГО!!!! + }else if(Blockchain.Ethereum == chain || Blockchain.EthereumTestNet == chain) { + return new EthEngine(); + } + else if(Blockchain.Token == chain) { + return new TokenEngine(); + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java b/app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java new file mode 100644 index 0000000000..0bb59e9f6b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ConfirmPaymentActivity.java @@ -0,0 +1,732 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.Html; +import android.text.Spanned; +import android.text.TextWatcher; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RadioGroup; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +import org.json.JSONException; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +public class ConfirmPaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback { + + private static final int REQUEST_CODE_SIGN_PAYMENT = 1; + private static final int REQUEST_CODE_REQUEST_PIN2 = 2; + Button btnSend; + boolean feeRequestSuccess = false; + boolean balanceRequestSuccess = false; + EditText etWallet; + TextView tvCardID, tvBalance, tvCurrency, tvCurrency2, tvBalanceEquivalent, tvAmountEquivalent, tvFeeEquivalent; + EditText etAmount; + EditText etFee; + ImageView ivCamera; + Tangem_Card mCard; + RadioGroup rgFee; + String minFee = null, maxFee = null, normalFee = null; + Long minFeeInInternalUnits = 0L; + private NfcManager mNfcManager; + int requestPIN2Count = 0; + ProgressBar progressBar; + boolean nodeCheck = false; + Date dtVerifyed=null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_confirm_payment); + + MainActivity.commonInit(getApplicationContext()); + mNfcManager = new NfcManager(this, this); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + progressBar = findViewById(R.id.progressBar); + + btnSend = findViewById(R.id.btnSend); + etWallet = findViewById(R.id.etWallet); + tvCardID = findViewById(R.id.tvCardID); + tvBalance = findViewById(R.id.tvBalance); + tvCurrency = findViewById(R.id.tvCurrency); + tvCurrency2 = findViewById(R.id.tvCurrency2); + etAmount = findViewById(R.id.etAmount); + etFee = findViewById(R.id.etFee); + tvBalanceEquivalent = findViewById(R.id.tvBalanceEquivalent); + tvAmountEquivalent = findViewById(R.id.tvAmountEquivalent); + + tvFeeEquivalent = findViewById(R.id.tvFeeEquivalent); + ivCamera = findViewById(R.id.ivCamera); + + rgFee = findViewById(R.id.rgFee); + rgFee.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + doSetFee(checkedId); + } + }); + + etAmount.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + tvAmountEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, etAmount.getText().toString())); + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvAmountEquivalent.setError("Service unavailable"); + } else { + tvAmountEquivalent.setError(null); + } + } catch (Exception e) { + e.printStackTrace(); + tvAmountEquivalent.setText(""); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + etFee.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + String eqFee = engine.EvaluteFeeEquivalent(mCard, etFee.getText().toString()); + tvFeeEquivalent.setText(eqFee); + + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvFeeEquivalent.setError("Service unavailable"); + } else { + tvFeeEquivalent.setError(null); + } + } catch (Exception e) { + e.printStackTrace(); + tvFeeEquivalent.setText(""); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + //tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + if (mCard.getBlockchain() == Blockchain.Token) { + Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard)); + tvBalance.setText(html); + } else { + tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + } + etAmount.setText(getIntent().getStringExtra("Amount")); + tvCurrency.setText(engine.GetBalanceCurrency(mCard)); + tvCurrency2.setText(engine.GetFeeCurrency()); + + tvCardID.setText(mCard.getCIDDescription()); + + //tvBalanceEquivalent.setText(mCard.getBalanceEquivalentDescription()); + tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard)); + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvBalanceEquivalent.setError("Service unavailable"); + } else { + tvBalanceEquivalent.setError(null); + } + + etWallet.setText(getIntent().getStringExtra("Wallet")); + + etFee.setText("?"); + + btnSend.setVisibility(View.INVISIBLE); + feeRequestSuccess = false; + balanceRequestSuccess = false; + btnSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + Calendar calendar=Calendar.getInstance(); + calendar.add(Calendar.MINUTE,-1); + + if( dtVerifyed==null || dtVerifyed.before(calendar.getTime()) ) { + FinishActivityWithError(Activity.RESULT_CANCELED, "The obtained data is outdated! Try again"); + return; + } + + CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (engineCoin.IsNeedCheckNode() && !nodeCheck) { + Toast.makeText(getBaseContext(), "Cannot reach current active blockchain node. Try again", Toast.LENGTH_LONG).show(); + return; + } + String txFee = etFee.getText().toString(); + String txAmount = etAmount.getText().toString(); + + + if (!engineCoin.HasBalanceInfo(mCard)) { + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes"); + return; + } else if (!engineCoin.IsBalanceNotZero(mCard)) { + FinishActivityWithError(Activity.RESULT_CANCELED, "The wallet is empty"); + return; + } else if(!engineCoin.CheckUnspentTransaction(mCard)) { + //else if (mCard.getUnspentTransactions().size() == 0 && mCard.getBlockchain() != Blockchain.Ethereum) { + FinishActivityWithError(Activity.RESULT_CANCELED, "Please wait for confirmation of incoming transaction"); + return; + } + + if(!engineCoin.CheckAmountValie(mCard, txAmount, txFee, minFeeInInternalUnits)) + { + FinishActivityWithError(Activity.RESULT_CANCELED, "Fee exceeds payment amount. Enter correct value and repeat sending."); + return; + } + + requestPIN2Count = 0; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + } + }); + + if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request req = Infura_Request.GetGasPrise(mCard.getWallet()); + req.setID(67); + req.setBlockchain(mCard.getBlockchain()); + rgFee.setEnabled(false); + task.execute(req); + } else { + + rgFee.setEnabled(true); + + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + + CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); + + for(int i =0 ; i < data.allRequest; ++i) { + + String nodeAddress = engineCoin.GetNextNode(mCard); + int nodePort = engineCoin.GetNextNodePort(mCard); + //ConnectTask connectTaskEx = new ConnectTask(Blockchain.getNextServiceHost(mCard), Blockchain.getNextServicePort(mCard), data); + ConnectTask connectTaskEx = new ConnectTask(nodeAddress, nodePort, data); + + //connectTaskEx.execute(Electrum_Request.CheckBalance(mCard.getWallet())); + connectTaskEx.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR,Electrum_Request.CheckBalance(mCard.getWallet())); + } + + String nodeAddress = engineCoin.GetNode(mCard); + int nodePort = engineCoin.GetNodePort(mCard); + ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort, data); + + //ConnectTask connectTask = new ConnectTask(Blockchain.getServiceHost(mCard), Blockchain.getServicePort(mCard)); + + connectTask.execute(/*Electrum_Request.CheckBalance(mCard.getWallet()), */Electrum_Request.GetFee(mCard.getWallet())); + + int calcSize = 256; + try { + calcSize = BuildSize(etWallet.getText().toString(), "0.00", etAmount.getText().toString()); + } catch (Exception ex) { + Log.e("Build Fee error", ex.getMessage()); + } + + SharedData sharedFee = new SharedData(SharedData.COUNT_REQUEST); + + progressBar.setVisibility(View.VISIBLE); + for(int i = 0; i < SharedData.COUNT_REQUEST; ++i) + { + + ConnectFeeTask feeTask = new ConnectFeeTask(sharedFee); + + feeTask.execute(Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.NORMAL), + Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.MINIMAL), + Fee_Request.GetFee(mCard.getWallet(), calcSize, Fee_Request.PRIORITY)); + } + } + + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + Intent intent = new Intent(); + intent.putExtra("message", "Operation canceled"); + setResult(Activity.RESULT_CANCELED, intent); + finish(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_SIGN_PAYMENT) { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard=new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard=updatedCard; + } + if (resultCode == SignPaymentActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + return; + } + setResult(resultCode, data); + finish(); + } else if (requestCode == REQUEST_CODE_REQUEST_PIN2) { + if (resultCode == Activity.RESULT_OK) { + Intent intent = new Intent(getBaseContext(), SignPaymentActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + intent.putExtra("Wallet", etWallet.getText().toString()); + intent.putExtra("Amount", etAmount.getText().toString()); + intent.putExtra("Fee", etFee.getText().toString()); + startActivityForResult(intent, REQUEST_CODE_SIGN_PAYMENT); + } else { + Toast.makeText(getBaseContext(), "PIN2 is required to sign the payment", Toast.LENGTH_LONG).show(); + } + } + } + + void FinishActivityWithError(int errorCode, String message) { + //Snackbar.make(etFee, message, Snackbar.LENGTH_LONG).show(); + Intent intent = new Intent(); + intent.putExtra("message", message); + setResult(errorCode, intent); + finish(); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(), "Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + int BuildSize(String outputAddress, String outFee, String outAmount) throws Exception { + String myAddress = mCard.getWallet(); + String changeAddress = myAddress; //"n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"; + byte[] pbKey = mCard.getWalletPublicKey(); + byte[] pbComprKey = mCard.getWalletPublicKeyRar(); + + // Build script for our address + List rawTxList = mCard.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + // Get first unspent + UnspentOutputInfo outPut = unspentOutputs.get(0); + int outPutIndex = outPut.outputIndex; + + // get prev TX id; + String prevTXID = rawTxList.get(0).txID;//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2"; + + long fees = FormatUtil.ConvertStringToLong(outFee); + long amount = FormatUtil.ConvertStringToLong(outAmount); + amount = amount - fees; + + long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] hashesForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, changeAddress, unspentOutputs, i, amount, change); + + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); + + Log.e("TX_BODY_1", BTCUtils.toHex(newTX)); + Log.e("TX_HASH_1", BTCUtils.toHex(hashData)); + Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData)); + + unspentOutputs.get(i).bodyDoubleHash = doubleHashData; + unspentOutputs.get(i).bodyHash = hashData; + + hashesForSign[i] = doubleHashData; + } + + byte[] signFromCard = new byte[64 * unspentOutputs.size()]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, changeAddress, unspentOutputs, amount, change); + + return realTX.length; + } + + private class ConnectTask extends Electrum_Task { + public ConnectTask(String host, int port) { + super(host, port); + } + + public ConnectTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Electrum_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_GetBalance)) { + try { + etFee.setText("--"); + + //String mWalletAddress = request.getParams().getString(0); + if ((request.getResult().getInt("confirmed") + request.getResult().getInt("unconfirmed")) / mCard.getBlockchain().getMultiplier() * 1000000.0 < Float.parseFloat(etAmount.getText().toString())) { + etFee.setError("Not enough funds"); + balanceRequestSuccess = false; + btnSend.setVisibility(View.INVISIBLE); + dtVerifyed=null; + nodeCheck = false; + } else { + etFee.setError(null); + balanceRequestSuccess = true; + if(feeRequestSuccess && balanceRequestSuccess) { + btnSend.setVisibility(View.VISIBLE); + + } + dtVerifyed=new Date(); + nodeCheck = true; + } + } catch (JSONException e) { + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes"); + } + } + else + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot check balance! No connection with blockchain nodes"); + } + } + } else if (request.isMethod(Electrum_Request.METHOD_GetFee)) { + if (request.getResultString() == "-1") { + etFee.setText("3"); + } + } + } else { +// etFee.setError(request.error); +// btnSend.setVisibility(View.INVISIBLE); + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + return; + } + } catch (JSONException e) { + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + + } + } + else + { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + } + + } + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain){ + super(blockchain); + } + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + Long price = 0L; + if (request.error == null) { + + if (request.isMethod(Infura_Request.METHOD_ETH_GetGasPrice)) { + try { + String gasPrice = request.getResultString(); + gasPrice = gasPrice.substring(2); + BigInteger l = new BigInteger(gasPrice, 16); + + BigInteger m = mCard.getBlockchain() == Blockchain.Token ? BigInteger.valueOf(55000) : BigInteger.valueOf(21000); + l = l.multiply(m); + String feeInGwei = mCard.getAmountInGwei(String.valueOf(l)); + + minFee=feeInGwei; + maxFee=feeInGwei; + normalFee=feeInGwei; + etFee.setText(feeInGwei); + etFee.setError(null); + btnSend.setVisibility(View.VISIBLE); + feeRequestSuccess = true; + balanceRequestSuccess = true; + + dtVerifyed=new Date(); + minFeeInInternalUnits = mCard.InternalUnitsFromString(feeInGwei); + + } catch (JSONException e) { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes"); + } + } + } else { + FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes"); + } + } catch (JSONException e) { + e.printStackTrace(); + FinishActivityWithError(Activity.RESULT_CANCELED, "Can't calculate fee! No connection with blockchain nodes"); + } + } + } + } + + private class ConnectFeeTask extends Fee_Task { + public ConnectFeeTask(SharedData sharedData) { + super(sharedData); + } + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Fee_Request request : requests) { + if (request.error == null) { + long minFeeRate = 0; + + try { + + try { + String tmpAnswer = request.getAsString(); + BigDecimal minFeeBD = new BigDecimal(tmpAnswer); + BigDecimal multiplicator = new BigDecimal("100000000"); + minFeeBD = minFeeBD.multiply(multiplicator); + BigInteger minFeeBI = minFeeBD.toBigInteger(); + minFeeRate = minFeeBI.longValue(); + } catch (Exception e) { + + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + + + if(errCounter >= sharedCounter.allRequest) + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + + //FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + return; + } + + if (minFeeRate == 0) { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! Wrong data received from the node"); + return; + } + + long inputCount = request.txSize; + + if (inputCount != 0) { + minFeeRate = minFeeRate * inputCount; + } else { + minFeeRate = minFeeRate * 256; + } + } catch (Exception e) { + e.printStackTrace(); + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + return; + } + + progressBar.setVisibility(View.INVISIBLE); + + float finalFee = (float) minFeeRate / (float) 10000; + + finalFee = Math.round(finalFee) / (float) 10000; + + if (request.getBlockCount() == Fee_Request.MINIMAL) { + minFee = String.valueOf(finalFee); + minFeeInInternalUnits = mCard.InternalUnitsFromString(String.valueOf(finalFee)); + } else if (request.getBlockCount() == Fee_Request.NORMAL) { + normalFee = String.valueOf(finalFee); + } else if (request.getBlockCount() == Fee_Request.PRIORITY) { + maxFee = String.valueOf(finalFee); + } + + doSetFee(rgFee.getCheckedRadioButtonId()); + + etFee.setError(null); + feeRequestSuccess = true; + if(feeRequestSuccess && balanceRequestSuccess) { + btnSend.setVisibility(View.VISIBLE); + } + dtVerifyed=new Date(); + + } else { + + if(sharedCounter != null) + { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if(errCounter >= sharedCounter.allRequest) + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + else + { + progressBar.setVisibility(View.INVISIBLE); + FinishActivityWithError(Activity.RESULT_CANCELED, "Cannot calculate fee! No connection with blockchain nodes"); + } + } + } + } + } + + private void doSetFee(int checkedRadioButtonId) { + switch (checkedRadioButtonId) { + case R.id.rbMinimalFee: + if (minFee != null) etFee.setText(minFee); + else etFee.setText("?"); + break; + case R.id.rbNormalFee: + if (normalFee != null) etFee.setText(normalFee); + else etFee.setText("?"); + break; + case R.id.rbMaximumFee: + if (maxFee != null) etFee.setText(maxFee); + else etFee.setText("?"); + break; + } + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java b/app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java new file mode 100644 index 0000000000..0c55114850 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CreateNewWalletActivity.java @@ -0,0 +1,330 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class CreateNewWalletActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; + private Tangem_Card mCard; + private TextView tvCardID; + private NfcManager mNfcManager; + private static final String logTag = "CreateNewActivity"; + private ProgressBar progressBar; + private CreateNewWalletTask createNewWalletTask; + private boolean lastReadSuccess = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_create_new_wallet); + + MainActivity.commonInit(getApplicationContext()); + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + tvCardID = (TextView) findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + mNfcManager = new NfcManager(this, this); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + Log.v(logTag, "UID: " + sUID); + + if (sUID.equals(mCard.getUID())) { + if (lastReadSuccess) { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 5000); + } else { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); + } + createNewWalletTask = new CreateNewWalletTask(isoDep, this); + createNewWalletTask.start(); + } else { + Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (createNewWalletTask != null) { + createNewWalletTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (createNewWalletTask != null) { + createNewWalletTask.cancel(true); + } + super.onStop(); + } + +// @Override +// public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) { +// return mNfcManager.ShowNFCEnableDialog(); //onCreateDialog(id, builder, li); +// } + + private class CreateNewWalletTask extends Thread { + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + public CreateNewWalletTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); + mNotifications.OnReadStart(protocol); + try { + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + mNotifications.OnReadProgress(protocol, 5); + + Log.i("CreateNewWalletTask", "[-- Start create new wallet --]"); + + if (isCancelled) return; + protocol.run_VerifyCard(); + + Log.i("CreateNewWalletTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); + + mNotifications.OnReadProgress(protocol, 30); + if (isCancelled) return; + +// if (mCard.getPauseBeforePIN2() > 0) { +// mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); +// } +// try { + protocol.run_CreateWallet(PINStorage.getPIN2()); +// } finally { +// mNotifications.OnReadWait(0); +// } + mNotifications.OnReadProgress(protocol, 60); + if (isCancelled) return; + + protocol.run_Read(); + + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("CreateNewWalletTask", "[-- Finish create new wallet --]"); + mNotifications.OnReadFinish(protocol); + } + } + + public void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + createNewWalletTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } else { + lastReadSuccess = false; + if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + Intent intent = new Intent(); + intent.putExtra("message", "Cannot create wallet. Make sure you enter correct PIN2!"); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(RESULT_INVALID_PIN, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + return; + } else { + progressBar.post(new Runnable() { + @Override + public void run() { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + + } + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + createNewWalletTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } +} + diff --git a/app/src/main/java/com/tangem/wallet/CryptoUtil.java b/app/src/main/java/com/tangem/wallet/CryptoUtil.java new file mode 100644 index 0000000000..689d0d7eec --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/CryptoUtil.java @@ -0,0 +1,270 @@ +package com.tangem.wallet; + +import android.util.Log; + +import com.tangem.cardReader.Util; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static org.bitcoinj.core.ECKey.CURVE; +import static org.bitcoinj.core.ECKey.HALF_CURVE_ORDER; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class CryptoUtil { + + public static boolean checkHashSign2(byte[] pub, byte[] hash, BigInteger r, BigInteger s) + { + ECDSASigner signer = new ECDSASigner(); + + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + return signer.verifySignature(hash, r, s); + } + + public static boolean isCanonical(BigInteger s) { + return s.compareTo(HALF_CURVE_ORDER) <= 0; + } + + public static BigInteger toCanonicalised(BigInteger s) { + + // The order of the curve is the number of valid points that exist on that curve. If S is in the upper + // half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. + // 10 - 8 == 2, giving us always the latter solution, which is canonical. + if(!isCanonical(s)) { + BigInteger canon = CURVE.getN().subtract(s); + Log.e("TX_SIGN", "non Canonical S"); + return canon; + } + + return s; + + } + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return CURVE.getCurve().decodePoint(compEnc); + } + + public static byte[] recoverPubBytesFromSignature(int recId, ECDSASignature_ETH sig, byte[] messageHash) { + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) + // 1.1 Let x = r + jn + + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + ECDomainParameters CURVE2 = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + + BigInteger n = CURVE2.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed public key. + ECCurve.Fp curve = (ECCurve.Fp) CURVE2.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. + return null; + } + // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) + return null; + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. + BigInteger e = new BigInteger(1, messageHash); + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group operator). + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE2.getG(), eInvrInv, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + public static byte[] calcSign(byte[] priv, byte[] hash) + { + ECDSASigner signer = new ECDSASigner(); + BigInteger d = new BigInteger(priv); + ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE); + signer.init(true, params); + BigInteger[] rs = signer.generateSignature(hash); + byte[] r = rs[0].toByteArray(); + byte[] s = rs[1].toByteArray(); + byte[] sign = new byte[64]; + for(int i = 0; i < 32; ++i) + { + sign[i] = r[i]; + sign[i+32] = s[i]; + } + return sign; + + } + + public static boolean isEncodingCanonical(byte[] signature) { + // See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + // A canonical signature exists of: <30> <02> <02> + // 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; + } +} diff --git a/app/src/main/java/com/tangem/wallet/DerEncodingUtil.java b/app/src/main/java/com/tangem/wallet/DerEncodingUtil.java new file mode 100644 index 0000000000..8d0afcfb60 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DerEncodingUtil.java @@ -0,0 +1,130 @@ +package com.tangem.wallet; + +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequenceGenerator; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class DerEncodingUtil { + + public static byte[] PackInteger(byte[] s) + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write((byte)0x02); + + byte length = (byte)s.length; + if (s[0] > 0x7f) { + baos.write((byte)(length+1)); + baos.write((byte) 0x00); + } + else { + baos.write((byte)length); + } + + for(int i = 0; i < length; ++i) + baos.write(s[i]); + + return baos.toByteArray(); + } + + public static byte[] packSignDer(BigInteger r, BigInteger s, byte[] pubKey) throws IOException + { + byte[] signDer = DerEncoding(r, s); + BitcoinOutputStream packKey = new BitcoinOutputStream(); + + packKey.write((byte)0x41); + packKey.write(pubKey); + + byte[] keyArray = packKey.toByteArray(); + + BitcoinOutputStream result = new BitcoinOutputStream(); + result.write((byte)(signDer.length+1)); + result.write(signDer); + result.write((byte)0x1); + + result.write(keyArray); + + return result.toByteArray(); + + } + + public static byte[] packSignDerBitcoinCash(BigInteger r, BigInteger s, byte[] pubKey) throws IOException + { + byte[] signDer = DerEncoding(r, s); + BitcoinOutputStream packKey = new BitcoinOutputStream(); + + packKey.write((byte)0x21); //compress key + packKey.write(pubKey); + + byte[] keyArray = packKey.toByteArray(); + + BitcoinOutputStream result = new BitcoinOutputStream(); + result.write((byte)(signDer.length+1)); + result.write(signDer); + result.write((byte)0x41); + + result.write(keyArray); + + return result.toByteArray(); + + } + + + public static byte[] DerEncoding(BigInteger r, BigInteger s) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(72); + DERSequenceGenerator seq = new DERSequenceGenerator(bos); + seq.addObject(new ASN1Integer(r)); + seq.addObject(new ASN1Integer(s)); + seq.close(); + return bos.toByteArray(); + } + + public static byte[] DerEncoding(byte[] sign) + { + byte[] r = sign; + byte[] s = new byte[32]; + for(int i =0; i < 32; ++i) + { + s[i] = sign[i+32]; + } + + byte[] newR = PackInteger(r); + byte[] newS = PackInteger(s); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write((byte)(newR.length+newS.length+2)); + baos.write((byte)newR.length); + baos.write(newR, 0, newR.length); + baos.write((byte)newS.length); + baos.write(newS, 0, newS.length); + + return baos.toByteArray(); + } + + public static byte[] DerEncodingBI(BigInteger[] sign) + { + byte[] r = sign[0].toByteArray(); + byte[] s = sign[1].toByteArray(); + + byte[] newR = PackInteger(r); + byte[] newS = PackInteger(s); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + baos.write((byte)(newR.length+newS.length+2)); + + baos.write((byte)newR.length); + baos.write(newR, 0, newR.length); + + baos.write((byte)newS.length); + baos.write(newS, 0, newS.length); + + return baos.toByteArray(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java b/app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java new file mode 100644 index 0000000000..f9279eb23f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DeviceNFCAntennaLocation.java @@ -0,0 +1,25 @@ +package com.tangem.wallet; + +import android.os.Build; + +public class DeviceNFCAntennaLocation { + + public float X; + public float Y; + public boolean OnBackSide; + public float Strength; + + public void getAntennaLocation() { + String device = DeviceName.getDeviceName(); + String model = Build.DEVICE; + this.X = 0.5f; + this.Y = 0.33f; + this.OnBackSide = true; + this.Strength = 1.0f; + // Samsung +// if (model.contains("Samsung")) {this.Y = 0.33; this.Strength = 0.5; } +// if (model.contains("Sony")) {this.Y = 0.33; this.Strength = 0.5; } +// if (device == "Galaxy J5") {this.Y = 0.4; this.Strength = 0.5; } + if (device == "P10 lite") {this.Y = 0.03f; this.Strength = 0.8f; } + } +} diff --git a/app/src/main/java/com/tangem/wallet/DeviceName.java b/app/src/main/java/com/tangem/wallet/DeviceName.java new file mode 100644 index 0000000000..ef1b1401b1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DeviceName.java @@ -0,0 +1,2091 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 20.04.2018. + */ +/* + * Copyright (C) 2017 Jared Rummler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + import android.Manifest; + import android.content.Context; + import android.content.SharedPreferences; + import android.content.pm.PackageManager; + import android.net.ConnectivityManager; + import android.net.NetworkInfo; + import android.os.Build; + import android.os.Handler; + import android.os.Looper; + import android.support.annotation.WorkerThread; + import android.text.TextUtils; + import java.io.BufferedReader; + import java.io.IOException; + import java.io.InputStreamReader; + import java.net.HttpURLConnection; + import java.net.URL; + import java.util.Locale; + import org.json.JSONArray; + import org.json.JSONException; + import org.json.JSONObject; + +// @formatter:off +/** + *

Get the consumer friendly name of an Android device.

+ * + *

On many popular devices the market name of the device is not available. For example, on the + * Samsung Galaxy S6 the value of {@link Build#MODEL} could be "SM-G920F", "SM-G920I", "SM-G920W8", + * etc.

+ * + *

See the usages below to get the consumer friends name of a device:

+ * + *

Get the name of the current device:

+ * + *
+ * String deviceName = DeviceName.getDeviceName();
+ * 
+ * + *

The above code will get the correct device name for the top 600 Android devices. If the + * device is unrecognized, then Build.MODEL is returned.

+ * + *

Get the name of a device using the device's codename:

+ * + *
+ * // Retruns "Moto X Style"
+ * DeviceName.getDeviceName("clark", "Unknown device");
+ * 
+ * + *

Get information about the device:

+ * + *
+ * DeviceName.with(context).request(new DeviceName.Callback() {
+ *
+ *   @Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
+ *     String manufacturer = info.manufacturer;  // "Samsung"
+ *     String name = info.marketName;            // "Galaxy S6 Edge"
+ *     String model = info.model;                // "SM-G925I"
+ *     String codename = info.codename;          // "zerolte"
+ *     String deviceName = info.getName();       // "Galaxy S6 Edge"
+ *     // FYI: We are on the UI thread.
+ *   }
+ * });
+ * 
+ * + *

The above code loads JSON from a generated list of device names based on Google's maintained + * list. It will be up-to-date with Google's supported device list so that you will get the correct + * name for new or unknown devices. This supports over 10,000 devices.

+ * + *

This will only make a network call once. The value is saved to SharedPreferences for future + * calls.

+ */ + +public class DeviceName { + + // @formatter:on + + // JSON which is derived from Google's PDF document which contains all devices on Google Play. + // To get the URL to the JSON file which contains information about the device name: + // String url = String.format(DEVICE_JSON_URL, Build.DEVICE); + private static final String DEVICE_JSON_URL = + "https://raw.githubusercontent.com/jaredrummler/AndroidDeviceNames/master/json/devices/%s.json"; + + // Preference filename for storing device info so we don't need to download it again. + private static final String SHARED_PREF_NAME = "device_names"; + + /** + * Create a new request to get information about a device. + * + * @param context + * the application context + * @return a new Request instance. + */ + public static Request with(Context context) { + return new Request(context.getApplicationContext()); + } + + /** + * Get the consumer friendly name of the device. + * + * @return the market name of the current device. + * @see #getDeviceName(String, String) + */ + public static String getDeviceName() { + return getDeviceName(Build.DEVICE, Build.MODEL, capitalize(Build.MODEL)); + } + + /** + * Get the consumer friendly name of a device. + * + * @param codename + * the value of the system property "ro.product.device" ({@link Build#DEVICE}) + * or + * the value of the system property "ro.product.model" ({@link Build#MODEL}) + * @param fallback + * the fallback name if the device is unknown. Usually the value of the system property + * "ro.product.model" ({@link Build#MODEL}) + * @return the market name of a device or {@code fallback} if the device is unknown. + */ + public static String getDeviceName(String codename, String fallback) { + return getDeviceName(codename, codename, fallback); + } + + /** + * Get the consumer friendly name of a device. + * + * @param codename + * the value of the system property "ro.product.device" ({@link Build#DEVICE}). + * @param model + * the value of the system property "ro.product.model" ({@link Build#MODEL}). + * @param fallback + * the fallback name if the device is unknown. Usually the value of the system property + * "ro.product.model" ({@link Build#MODEL}) + * @return the market name of a device or {@code fallback} if the device is unknown. + */ + public static String getDeviceName(String codename, String model, String fallback) { + // ---------------------------------------------------------------------------- + // Acer + if ((codename != null && codename.equals("acer_S57")) + || (model != null && model.equals("S57"))) { + return "Liquid Jade Z"; + } + if ((codename != null && codename.equals("acer_t08")) + || (model != null && model.equals("T08"))) { + return "Liquid Zest Plus"; + } + // ---------------------------------------------------------------------------- + // Asus + if ((codename != null && (codename.equals("grouper") + || codename.equals("tilapia")))) { + return "Nexus 7 (2012)"; + } + if ((codename != null && (codename.equals("deb") + || codename.equals("flo")))) { + return "Nexus 7 (2013)"; + } + // ---------------------------------------------------------------------------- + // Google + if ((codename != null && codename.equals("sailfish"))) { + return "Pixel"; + } + if ((codename != null && codename.equals("walleye"))) { + return "Pixel 2"; + } + if ((codename != null && codename.equals("taimen"))) { + return "Pixel 2 XL"; + } + if ((codename != null && codename.equals("dragon"))) { + return "Pixel C"; + } + if ((codename != null && codename.equals("marlin"))) { + return "Pixel XL"; + } + // ---------------------------------------------------------------------------- + // HTC + if ((codename != null && codename.equals("flounder"))) { + return "Nexus 9"; + } + // ---------------------------------------------------------------------------- + // Huawei + if ((codename != null && (codename.equals("HWBND-H"))) + || (model != null && (model.equals("BND-L21") + || model.equals("BND-L24")))) { + return "Honor 7X"; + } + if (model != null && model.contains("WAS-LX")) { + return "P10 lite"; + } + if ((codename != null && codename.equals("HWBKL")) + || (model != null && model.equals("BKL-L09"))) { + return "Honor View 10"; + } + if ((codename != null && (codename.equals("HWALP"))) + || (model != null && (model.equals("ALP-AL00") + || model.equals("ALP-L09") + || model.equals("ALP-L29") + || model.equals("ALP-TL00")))) { + return "Mate 10"; + } + if ((codename != null && (codename.equals("HWMHA"))) + || (model != null && (model.equals("MHA-AL00") + || model.equals("MHA-L09") + || model.equals("MHA-L29") + || model.equals("MHA-TL00")))) { + return "Mate 9"; + } + if ((codename != null && codename.equals("angler"))) { + return "Nexus 6P"; + } + // ---------------------------------------------------------------------------- + // LGE + if ((codename != null && (codename.equals("g2"))) + || (model != null && (model.equals("LG-D800") + || model.equals("LG-D801") + || model.equals("LG-D802") + || model.equals("LG-D802T") + || model.equals("LG-D802TR") + || model.equals("LG-D803") + || model.equals("LG-D805") + || model.equals("LG-D806") + || model.equals("LG-F320K") + || model.equals("LG-F320L") + || model.equals("LG-F320S") + || model.equals("LG-LS980") + || model.equals("VS980 4G")))) { + return "LG G2"; + } + if ((codename != null && (codename.equals("g3"))) + || (model != null && (model.equals("AS985") + || model.equals("LG-AS990") + || model.equals("LG-D850") + || model.equals("LG-D851") + || model.equals("LG-D852") + || model.equals("LG-D852G") + || model.equals("LG-D855") + || model.equals("LG-D856") + || model.equals("LG-D857") + || model.equals("LG-D858") + || model.equals("LG-D858HK") + || model.equals("LG-D859") + || model.equals("LG-F400K") + || model.equals("LG-F400L") + || model.equals("LG-F400S") + || model.equals("LGL24") + || model.equals("LGLS990") + || model.equals("LGUS990") + || model.equals("LGV31") + || model.equals("VS985 4G")))) { + return "LG G3"; + } + if ((codename != null && (codename.equals("p1"))) + || (model != null && (model.equals("AS986") + || model.equals("LG-AS811") + || model.equals("LG-AS991") + || model.equals("LG-F500K") + || model.equals("LG-F500L") + || model.equals("LG-F500S") + || model.equals("LG-H810") + || model.equals("LG-H811") + || model.equals("LG-H812") + || model.equals("LG-H815") + || model.equals("LG-H818") + || model.equals("LG-H819") + || model.equals("LGLS991") + || model.equals("LGUS991") + || model.equals("LGV32") + || model.equals("VS986")))) { + return "LG G4"; + } + if ((codename != null && (codename.equals("h1"))) + || (model != null && (model.equals("LG-F700K") + || model.equals("LG-F700L") + || model.equals("LG-F700S") + || model.equals("LG-H820") + || model.equals("LG-H820PR") + || model.equals("LG-H830") + || model.equals("LG-H831") + || model.equals("LG-H850") + || model.equals("LG-H858") + || model.equals("LG-H860") + || model.equals("LG-H868") + || model.equals("LGAS992") + || model.equals("LGLS992") + || model.equals("LGUS992") + || model.equals("RS988") + || model.equals("VS987")))) { + return "LG G5"; + } + if ((codename != null && (codename.equals("lucye"))) + || (model != null && (model.equals("LG-AS993") + || model.equals("LG-H870") + || model.equals("LG-H870AR") + || model.equals("LG-H870DS") + || model.equals("LG-H870I") + || model.equals("LG-H870S") + || model.equals("LG-H871") + || model.equals("LG-H872") + || model.equals("LG-H872PR") + || model.equals("LG-H873") + || model.equals("LG-LS993") + || model.equals("LGM-G600K") + || model.equals("LGM-G600L") + || model.equals("LGM-G600S") + || model.equals("LGUS997") + || model.equals("VS988")))) { + return "LG G6"; + } + if ((codename != null && codename.equals("mako"))) { + return "Nexus 4"; + } + if ((codename != null && codename.equals("hammerhead"))) { + return "Nexus 5"; + } + if ((codename != null && codename.equals("bullhead"))) { + return "Nexus 5X"; + } + // ---------------------------------------------------------------------------- + // Motorola + if ((codename != null && codename.equals("shamu"))) { + return "Nexus 6"; + } + // ---------------------------------------------------------------------------- + // OnePlus + if ((codename != null && codename.equals("OnePlus3")) + || (model != null && model.equals("ONEPLUS A3000"))) { + return "OnePlus3"; + } + if ((codename != null && codename.equals("OnePlus3T")) + || (model != null && model.equals("ONEPLUS A3000"))) { + return "OnePlus3T"; + } + if ((codename != null && codename.equals("OnePlus5")) + || (model != null && model.equals("ONEPLUS A5000"))) { + return "OnePlus5"; + } + if ((codename != null && codename.equals("OnePlus5T")) + || (model != null && model.equals("ONEPLUS A5010"))) { + return "OnePlus5T"; + } + // ---------------------------------------------------------------------------- + // Samsung + if ((codename != null && (codename.equals("a53g") + || codename.equals("a5lte") + || codename.equals("a5ltechn") + || codename.equals("a5ltectc") + || codename.equals("a5ltezh") + || codename.equals("a5ltezt") + || codename.equals("a5ulte") + || codename.equals("a5ultebmc") + || codename.equals("a5ultektt") + || codename.equals("a5ultelgt") + || codename.equals("a5ulteskt"))) + || (model != null && (model.equals("SM-A5000") + || model.equals("SM-A5009") + || model.equals("SM-A500F") + || model.equals("SM-A500F1") + || model.equals("SM-A500FU") + || model.equals("SM-A500G") + || model.equals("SM-A500H") + || model.equals("SM-A500K") + || model.equals("SM-A500L") + || model.equals("SM-A500M") + || model.equals("SM-A500S") + || model.equals("SM-A500W") + || model.equals("SM-A500X") + || model.equals("SM-A500XZ") + || model.equals("SM-A500Y") + || model.equals("SM-A500YZ")))) { + return "Galaxy A5"; + } + if ((codename != null && (codename.equals("vivaltods5m"))) + || (model != null && (model.equals("SM-G313HU") + || model.equals("SM-G313HY") + || model.equals("SM-G313M") + || model.equals("SM-G313MY")))) { + return "Galaxy Ace 4"; + } + if ((codename != null && (codename.equals("GT-S6352") + || codename.equals("GT-S6802") + || codename.equals("GT-S6802B") + || codename.equals("SCH-I579") + || codename.equals("SCH-I589") + || codename.equals("SCH-i579") + || codename.equals("SCH-i589"))) + || (model != null && (model.equals("GT-S6352") + || model.equals("GT-S6802") + || model.equals("GT-S6802B") + || model.equals("SCH-I589") + || model.equals("SCH-i579") + || model.equals("SCH-i589")))) { + return "Galaxy Ace Duos"; + } + if ((codename != null && (codename.equals("GT-S7500") + || codename.equals("GT-S7500L") + || codename.equals("GT-S7500T") + || codename.equals("GT-S7500W") + || codename.equals("GT-S7508"))) + || (model != null && (model.equals("GT-S7500") + || model.equals("GT-S7500L") + || model.equals("GT-S7500T") + || model.equals("GT-S7500W") + || model.equals("GT-S7508")))) { + return "Galaxy Ace Plus"; + } + if ((codename != null && (codename.equals("heat3gtfnvzw") + || codename.equals("heatnfc3g") + || codename.equals("heatqlte"))) + || (model != null && (model.equals("SM-G310HN") + || model.equals("SM-G357FZ") + || model.equals("SM-S765C") + || model.equals("SM-S766C")))) { + return "Galaxy Ace Style"; + } + if ((codename != null && (codename.equals("vivalto3g") + || codename.equals("vivalto3mve3g") + || codename.equals("vivalto5mve3g") + || codename.equals("vivaltolte") + || codename.equals("vivaltonfc3g"))) + || (model != null && (model.equals("SM-G313F") + || model.equals("SM-G313HN") + || model.equals("SM-G313ML") + || model.equals("SM-G313MU") + || model.equals("SM-G316H") + || model.equals("SM-G316HU") + || model.equals("SM-G316M") + || model.equals("SM-G316MY")))) { + return "Galaxy Ace4"; + } + if ((codename != null && (codename.equals("core33g") + || codename.equals("coreprimelte") + || codename.equals("coreprimelteaio") + || codename.equals("coreprimeltelra") + || codename.equals("coreprimeltespr") + || codename.equals("coreprimeltetfnvzw") + || codename.equals("coreprimeltevzw") + || codename.equals("coreprimeve3g") + || codename.equals("coreprimevelte") + || codename.equals("cprimeltemtr") + || codename.equals("cprimeltetmo") + || codename.equals("rossalte") + || codename.equals("rossaltectc") + || codename.equals("rossaltexsa"))) + || (model != null && (model.equals("SAMSUNG-SM-G360AZ") + || model.equals("SM-G3606") + || model.equals("SM-G3608") + || model.equals("SM-G3609") + || model.equals("SM-G360F") + || model.equals("SM-G360FY") + || model.equals("SM-G360GY") + || model.equals("SM-G360H") + || model.equals("SM-G360HU") + || model.equals("SM-G360M") + || model.equals("SM-G360P") + || model.equals("SM-G360R6") + || model.equals("SM-G360T") + || model.equals("SM-G360T1") + || model.equals("SM-G360V") + || model.equals("SM-G361F") + || model.equals("SM-G361H") + || model.equals("SM-G361HU") + || model.equals("SM-G361M") + || model.equals("SM-S820L")))) { + return "Galaxy Core Prime"; + } + if ((codename != null && (codename.equals("kanas") + || codename.equals("kanas3g") + || codename.equals("kanas3gcmcc") + || codename.equals("kanas3gctc") + || codename.equals("kanas3gnfc"))) + || (model != null && (model.equals("SM-G3556D") + || model.equals("SM-G3558") + || model.equals("SM-G3559") + || model.equals("SM-G355H") + || model.equals("SM-G355HN") + || model.equals("SM-G355HQ") + || model.equals("SM-G355M")))) { + return "Galaxy Core2"; + } + if ((codename != null && (codename.equals("e53g") + || codename.equals("e5lte") + || codename.equals("e5ltetfnvzw") + || codename.equals("e5ltetw"))) + || (model != null && (model.equals("SM-E500F") + || model.equals("SM-E500H") + || model.equals("SM-E500M") + || model.equals("SM-E500YZ") + || model.equals("SM-S978L")))) { + return "Galaxy E5"; + } + if ((codename != null && (codename.equals("e73g") + || codename.equals("e7lte") + || codename.equals("e7ltechn") + || codename.equals("e7ltectc") + || codename.equals("e7ltehktw"))) + || (model != null && (model.equals("SM-E7000") + || model.equals("SM-E7009") + || model.equals("SM-E700F") + || model.equals("SM-E700H") + || model.equals("SM-E700M")))) { + return "Galaxy E7"; + } + if ((codename != null && (codename.equals("SCH-I629") + || codename.equals("nevis") + || codename.equals("nevis3g") + || codename.equals("nevis3gcmcc") + || codename.equals("nevisds") + || codename.equals("nevisnvess") + || codename.equals("nevisp") + || codename.equals("nevisvess") + || codename.equals("nevisw"))) + || (model != null && (model.equals("GT-S6790") + || model.equals("GT-S6790E") + || model.equals("GT-S6790L") + || model.equals("GT-S6790N") + || model.equals("GT-S6810") + || model.equals("GT-S6810B") + || model.equals("GT-S6810E") + || model.equals("GT-S6810L") + || model.equals("GT-S6810M") + || model.equals("GT-S6810P") + || model.equals("GT-S6812") + || model.equals("GT-S6812B") + || model.equals("GT-S6812C") + || model.equals("GT-S6812i") + || model.equals("GT-S6818") + || model.equals("GT-S6818V") + || model.equals("SCH-I629")))) { + return "Galaxy Fame"; + } + if ((codename != null && codename.equals("grandprimelteatt")) + || (model != null && model.equals("SAMSUNG-SM-G530A"))) { + return "Galaxy Go Prime"; + } + if ((codename != null && (codename.equals("baffinlite") + || codename.equals("baffinlitedtv") + || codename.equals("baffinq3g"))) + || (model != null && (model.equals("GT-I9060") + || model.equals("GT-I9060L") + || model.equals("GT-I9063T") + || model.equals("GT-I9082C") + || model.equals("GT-I9168") + || model.equals("GT-I9168I")))) { + return "Galaxy Grand Neo"; + } + if ((codename != null && (codename.equals("fortuna3g") + || codename.equals("fortuna3gdtv") + || codename.equals("fortunalte") + || codename.equals("fortunaltectc") + || codename.equals("fortunaltezh") + || codename.equals("fortunaltezt") + || codename.equals("fortunave3g") + || codename.equals("gprimelteacg") + || codename.equals("gprimeltecan") + || codename.equals("gprimeltemtr") + || codename.equals("gprimeltespr") + || codename.equals("gprimeltetfnvzw") + || codename.equals("gprimeltetmo") + || codename.equals("gprimelteusc") + || codename.equals("grandprimelte") + || codename.equals("grandprimelteaio") + || codename.equals("grandprimeve3g") + || codename.equals("grandprimeve3gdtv") + || codename.equals("grandprimevelte") + || codename.equals("grandprimevelteltn") + || codename.equals("grandprimeveltezt"))) + || (model != null && (model.equals("SAMSUNG-SM-G530AZ") + || model.equals("SM-G5306W") + || model.equals("SM-G5308W") + || model.equals("SM-G5309W") + || model.equals("SM-G530BT") + || model.equals("SM-G530F") + || model.equals("SM-G530FZ") + || model.equals("SM-G530H") + || model.equals("SM-G530M") + || model.equals("SM-G530MU") + || model.equals("SM-G530P") + || model.equals("SM-G530R4") + || model.equals("SM-G530R7") + || model.equals("SM-G530T") + || model.equals("SM-G530T1") + || model.equals("SM-G530W") + || model.equals("SM-G530Y") + || model.equals("SM-G531BT") + || model.equals("SM-G531F") + || model.equals("SM-G531H") + || model.equals("SM-G531M") + || model.equals("SM-G531Y") + || model.equals("SM-S920L") + || model.equals("gprimelteacg")))) { + return "Galaxy Grand Prime"; + } + if ((codename != null && (codename.equals("ms013g") + || codename.equals("ms013gdtv") + || codename.equals("ms013gss") + || codename.equals("ms01lte") + || codename.equals("ms01ltektt") + || codename.equals("ms01ltelgt") + || codename.equals("ms01lteskt"))) + || (model != null && (model.equals("SM-G710") + || model.equals("SM-G7102") + || model.equals("SM-G7102T") + || model.equals("SM-G7105") + || model.equals("SM-G7105H") + || model.equals("SM-G7105L") + || model.equals("SM-G7106") + || model.equals("SM-G7108") + || model.equals("SM-G7109") + || model.equals("SM-G710K") + || model.equals("SM-G710L") + || model.equals("SM-G710S")))) { + return "Galaxy Grand2"; + } + if ((codename != null && (codename.equals("j13g") + || codename.equals("j13gtfnvzw") + || codename.equals("j1lte") + || codename.equals("j1nlte") + || codename.equals("j1qltevzw") + || codename.equals("j1xlte") + || codename.equals("j1xlteaio") + || codename.equals("j1xlteatt") + || codename.equals("j1xltecan") + || codename.equals("j1xqltespr") + || codename.equals("j1xqltetfnvzw"))) + || (model != null && (model.equals("SAMSUNG-SM-J120A") + || model.equals("SAMSUNG-SM-J120AZ") + || model.equals("SM-J100F") + || model.equals("SM-J100FN") + || model.equals("SM-J100G") + || model.equals("SM-J100H") + || model.equals("SM-J100M") + || model.equals("SM-J100ML") + || model.equals("SM-J100MU") + || model.equals("SM-J100VPP") + || model.equals("SM-J100Y") + || model.equals("SM-J120F") + || model.equals("SM-J120FN") + || model.equals("SM-J120M") + || model.equals("SM-J120P") + || model.equals("SM-J120W") + || model.equals("SM-S120VL") + || model.equals("SM-S777C")))) { + return "Galaxy J1"; + } + if ((codename != null && (codename.equals("j1acelte") + || codename.equals("j1acelteltn") + || codename.equals("j1acevelte") + || codename.equals("j1pop3g"))) + || (model != null && (model.equals("SM-J110F") + || model.equals("SM-J110G") + || model.equals("SM-J110H") + || model.equals("SM-J110L") + || model.equals("SM-J110M") + || model.equals("SM-J111F") + || model.equals("SM-J111M")))) { + return "Galaxy J1 Ace"; + } + if ((codename != null && (codename.equals("j53g") + || codename.equals("j5lte") + || codename.equals("j5ltechn") + || codename.equals("j5ltekx") + || codename.equals("j5nlte") + || codename.equals("j5ylte"))) + || (model != null && (model.equals("SM-J5007") + || model.equals("SM-J5008") + || model.equals("SM-J500F") + || model.equals("SM-J500FN") + || model.equals("SM-J500G") + || model.equals("SM-J500H") + || model.equals("SM-J500M") + || model.equals("SM-J500N0") + || model.equals("SM-J500Y")))) { + return "Galaxy J5"; + } + if ((codename != null && (codename.equals("j75ltektt") + || codename.equals("j7e3g") + || codename.equals("j7elte") + || codename.equals("j7ltechn"))) + || (model != null && (model.equals("SM-J7008") + || model.equals("SM-J700F") + || model.equals("SM-J700H") + || model.equals("SM-J700K") + || model.equals("SM-J700M")))) { + return "Galaxy J7"; + } + if ((codename != null && (codename.equals("maguro") + || codename.equals("toro") + || codename.equals("toroplus"))) + || (model != null && (model.equals("Galaxy X")))) { + return "Galaxy Nexus"; + } + if ((codename != null && (codename.equals("lt033g") + || codename.equals("lt03ltektt") + || codename.equals("lt03ltelgt") + || codename.equals("lt03lteskt") + || codename.equals("p4notelte") + || codename.equals("p4noteltektt") + || codename.equals("p4noteltelgt") + || codename.equals("p4notelteskt") + || codename.equals("p4noteltespr") + || codename.equals("p4notelteusc") + || codename.equals("p4noteltevzw") + || codename.equals("p4noterf") + || codename.equals("p4noterfktt") + || codename.equals("p4notewifi") + || codename.equals("p4notewifi43241any") + || codename.equals("p4notewifiany") + || codename.equals("p4notewifiktt") + || codename.equals("p4notewifiww"))) + || (model != null && (model.equals("GT-N8000") + || model.equals("GT-N8005") + || model.equals("GT-N8010") + || model.equals("GT-N8013") + || model.equals("GT-N8020") + || model.equals("SCH-I925") + || model.equals("SCH-I925U") + || model.equals("SHV-E230K") + || model.equals("SHV-E230L") + || model.equals("SHV-E230S") + || model.equals("SHW-M480K") + || model.equals("SHW-M480W") + || model.equals("SHW-M485W") + || model.equals("SHW-M486W") + || model.equals("SM-P601") + || model.equals("SM-P602") + || model.equals("SM-P605K") + || model.equals("SM-P605L") + || model.equals("SM-P605S") + || model.equals("SPH-P600")))) { + return "Galaxy Note 10.1"; + } + if ((codename != null && (codename.equals("SC-01G") + || codename.equals("SCL24") + || codename.equals("tbeltektt") + || codename.equals("tbeltelgt") + || codename.equals("tbelteskt") + || codename.equals("tblte") + || codename.equals("tblteatt") + || codename.equals("tbltecan") + || codename.equals("tbltechn") + || codename.equals("tbltespr") + || codename.equals("tbltetmo") + || codename.equals("tblteusc") + || codename.equals("tbltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N915A") + || model.equals("SC-01G") + || model.equals("SCL24") + || model.equals("SM-N9150") + || model.equals("SM-N915F") + || model.equals("SM-N915FY") + || model.equals("SM-N915G") + || model.equals("SM-N915K") + || model.equals("SM-N915L") + || model.equals("SM-N915P") + || model.equals("SM-N915R4") + || model.equals("SM-N915S") + || model.equals("SM-N915T") + || model.equals("SM-N915T3") + || model.equals("SM-N915V") + || model.equals("SM-N915W8") + || model.equals("SM-N915X")))) { + return "Galaxy Note Edge"; + } + if ((codename != null && (codename.equals("v1a3g") + || codename.equals("v1awifi") + || codename.equals("v1awifikx") + || codename.equals("viennalte") + || codename.equals("viennalteatt") + || codename.equals("viennaltekx") + || codename.equals("viennaltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-P907A") + || model.equals("SM-P900") + || model.equals("SM-P901") + || model.equals("SM-P905") + || model.equals("SM-P905F0") + || model.equals("SM-P905M") + || model.equals("SM-P905V")))) { + return "Galaxy Note Pro 12.2"; + } + if ((codename != null && (codename.equals("tre3caltektt") + || codename.equals("tre3caltelgt") + || codename.equals("tre3calteskt") + || codename.equals("tre3g") + || codename.equals("trelte") + || codename.equals("treltektt") + || codename.equals("treltelgt") + || codename.equals("trelteskt") + || codename.equals("trhplte") + || codename.equals("trlte") + || codename.equals("trlteatt") + || codename.equals("trltecan") + || codename.equals("trltechn") + || codename.equals("trltechnzh") + || codename.equals("trltespr") + || codename.equals("trltetmo") + || codename.equals("trlteusc") + || codename.equals("trltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N910A") + || model.equals("SM-N9100") + || model.equals("SM-N9106W") + || model.equals("SM-N9108V") + || model.equals("SM-N9109W") + || model.equals("SM-N910C") + || model.equals("SM-N910F") + || model.equals("SM-N910G") + || model.equals("SM-N910H") + || model.equals("SM-N910K") + || model.equals("SM-N910L") + || model.equals("SM-N910P") + || model.equals("SM-N910R4") + || model.equals("SM-N910S") + || model.equals("SM-N910T") + || model.equals("SM-N910T2") + || model.equals("SM-N910T3") + || model.equals("SM-N910U") + || model.equals("SM-N910V") + || model.equals("SM-N910W8") + || model.equals("SM-N910X") + || model.equals("SM-N916K") + || model.equals("SM-N916L") + || model.equals("SM-N916S")))) { + return "Galaxy Note4"; + } + if ((codename != null && (codename.equals("noblelte") + || codename.equals("noblelteacg") + || codename.equals("noblelteatt") + || codename.equals("nobleltebmc") + || codename.equals("nobleltechn") + || codename.equals("nobleltecmcc") + || codename.equals("nobleltehk") + || codename.equals("nobleltektt") + || codename.equals("nobleltelgt") + || codename.equals("nobleltelra") + || codename.equals("noblelteskt") + || codename.equals("nobleltespr") + || codename.equals("nobleltetmo") + || codename.equals("noblelteusc") + || codename.equals("nobleltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N920A") + || model.equals("SM-N9200") + || model.equals("SM-N9208") + || model.equals("SM-N920C") + || model.equals("SM-N920F") + || model.equals("SM-N920G") + || model.equals("SM-N920I") + || model.equals("SM-N920K") + || model.equals("SM-N920L") + || model.equals("SM-N920P") + || model.equals("SM-N920R4") + || model.equals("SM-N920R6") + || model.equals("SM-N920R7") + || model.equals("SM-N920S") + || model.equals("SM-N920T") + || model.equals("SM-N920V") + || model.equals("SM-N920W8") + || model.equals("SM-N920X")))) { + return "Galaxy Note5"; + } + if ((codename != null && (codename.equals("SC-01J") + || codename.equals("SCV34") + || codename.equals("gracelte") + || codename.equals("graceltektt") + || codename.equals("graceltelgt") + || codename.equals("gracelteskt") + || codename.equals("graceqlteacg") + || codename.equals("graceqlteatt") + || codename.equals("graceqltebmc") + || codename.equals("graceqltechn") + || codename.equals("graceqltedcm") + || codename.equals("graceqltelra") + || codename.equals("graceqltespr") + || codename.equals("graceqltetfnvzw") + || codename.equals("graceqltetmo") + || codename.equals("graceqlteue") + || codename.equals("graceqlteusc") + || codename.equals("graceqltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-N930A") + || model.equals("SC-01J") + || model.equals("SCV34") + || model.equals("SGH-N037") + || model.equals("SM-N9300") + || model.equals("SM-N930F") + || model.equals("SM-N930K") + || model.equals("SM-N930L") + || model.equals("SM-N930P") + || model.equals("SM-N930R4") + || model.equals("SM-N930R6") + || model.equals("SM-N930R7") + || model.equals("SM-N930S") + || model.equals("SM-N930T") + || model.equals("SM-N930U") + || model.equals("SM-N930V") + || model.equals("SM-N930VL") + || model.equals("SM-N930W8") + || model.equals("SM-N930X")))) { + return "Galaxy Note7"; + } + if ((codename != null && (codename.equals("SC-01K") + || codename.equals("SCV37") + || codename.equals("greatlte") + || codename.equals("greatlteks") + || codename.equals("greatqlte") + || codename.equals("greatqltechn") + || codename.equals("greatqltecmcc") + || codename.equals("greatqltecs") + || codename.equals("greatqlteue"))) + || (model != null && (model.equals("SC-01K") + || model.equals("SCV37") + || model.equals("SM-N9500") + || model.equals("SM-N9508") + || model.equals("SM-N950F") + || model.equals("SM-N950N") + || model.equals("SM-N950U") + || model.equals("SM-N950U1") + || model.equals("SM-N950W") + || model.equals("SM-N950XN")))) { + return "Galaxy Note8"; + } + if ((codename != null && (codename.equals("o5lte") + || codename.equals("o5ltechn") + || codename.equals("o5prolte") + || codename.equals("on5ltemtr") + || codename.equals("on5ltetfntmo") + || codename.equals("on5ltetmo"))) + || (model != null && (model.equals("SM-G5500") + || model.equals("SM-G550FY") + || model.equals("SM-G550T") + || model.equals("SM-G550T1") + || model.equals("SM-G550T2") + || model.equals("SM-S550TL")))) { + return "Galaxy On5"; + } + if ((codename != null && (codename.equals("o7lte") + || codename.equals("o7ltechn") + || codename.equals("on7elte"))) + || (model != null && (model.equals("SM-G6000") + || model.equals("SM-G600F") + || model.equals("SM-G600FY")))) { + return "Galaxy On7"; + } + if ((codename != null && (codename.equals("GT-I9000") + || codename.equals("GT-I9000B") + || codename.equals("GT-I9000M") + || codename.equals("GT-I9000T") + || codename.equals("GT-I9003") + || codename.equals("GT-I9003L") + || codename.equals("GT-I9008L") + || codename.equals("GT-I9010") + || codename.equals("GT-I9018") + || codename.equals("GT-I9050") + || codename.equals("SC-02B") + || codename.equals("SCH-I500") + || codename.equals("SCH-S950C") + || codename.equals("SCH-i909") + || codename.equals("SGH-I897") + || codename.equals("SGH-T959V") + || codename.equals("SGH-T959W") + || codename.equals("SHW-M110S") + || codename.equals("SHW-M190S") + || codename.equals("SPH-D700") + || codename.equals("loganlte"))) + || (model != null && (model.equals("GT-I9000") + || model.equals("GT-I9000B") + || model.equals("GT-I9000M") + || model.equals("GT-I9000T") + || model.equals("GT-I9003") + || model.equals("GT-I9003L") + || model.equals("GT-I9008L") + || model.equals("GT-I9010") + || model.equals("GT-I9018") + || model.equals("GT-I9050") + || model.equals("GT-S7275") + || model.equals("SAMSUNG-SGH-I897") + || model.equals("SC-02B") + || model.equals("SCH-I500") + || model.equals("SCH-S950C") + || model.equals("SCH-i909") + || model.equals("SGH-T959V") + || model.equals("SGH-T959W") + || model.equals("SHW-M110S") + || model.equals("SHW-M190S") + || model.equals("SPH-D700")))) { + return "Galaxy S"; + } + if ((codename != null && (codename.equals("kylechn") + || codename.equals("kyleopen") + || codename.equals("kyletdcmcc"))) + || (model != null && (model.equals("GT-S7562") + || model.equals("GT-S7568")))) { + return "Galaxy S Duos"; + } + if ((codename != null && (codename.equals("kyleprods"))) + || (model != null && (model.equals("GT-S7582") + || model.equals("GT-S7582L")))) { + return "Galaxy S Duos2"; + } + if ((codename != null && codename.equals("vivalto3gvn")) + || (model != null && model.equals("SM-G313HZ"))) { + return "Galaxy S Duos3"; + } + if ((codename != null && (codename.equals("SC-03E") + || codename.equals("c1att") + || codename.equals("c1ktt") + || codename.equals("c1lgt") + || codename.equals("c1skt") + || codename.equals("d2att") + || codename.equals("d2can") + || codename.equals("d2cri") + || codename.equals("d2dcm") + || codename.equals("d2lteMetroPCS") + || codename.equals("d2lterefreshspr") + || codename.equals("d2ltetmo") + || codename.equals("d2mtr") + || codename.equals("d2spi") + || codename.equals("d2spr") + || codename.equals("d2tfnspr") + || codename.equals("d2tfnvzw") + || codename.equals("d2tmo") + || codename.equals("d2usc") + || codename.equals("d2vmu") + || codename.equals("d2vzw") + || codename.equals("d2xar") + || codename.equals("m0") + || codename.equals("m0apt") + || codename.equals("m0chn") + || codename.equals("m0cmcc") + || codename.equals("m0ctc") + || codename.equals("m0ctcduos") + || codename.equals("m0skt") + || codename.equals("m3") + || codename.equals("m3dcm"))) + || (model != null && (model.equals("GT-I9300") + || model.equals("GT-I9300T") + || model.equals("GT-I9305") + || model.equals("GT-I9305N") + || model.equals("GT-I9305T") + || model.equals("GT-I9308") + || model.equals("Gravity") + || model.equals("GravityQuad") + || model.equals("SAMSUNG-SGH-I747") + || model.equals("SC-03E") + || model.equals("SC-06D") + || model.equals("SCH-I535") + || model.equals("SCH-I535PP") + || model.equals("SCH-I939") + || model.equals("SCH-I939D") + || model.equals("SCH-L710") + || model.equals("SCH-R530C") + || model.equals("SCH-R530M") + || model.equals("SCH-R530U") + || model.equals("SCH-R530X") + || model.equals("SCH-S960L") + || model.equals("SCH-S968C") + || model.equals("SGH-I747M") + || model.equals("SGH-I748") + || model.equals("SGH-T999") + || model.equals("SGH-T999L") + || model.equals("SGH-T999N") + || model.equals("SGH-T999V") + || model.equals("SHV-E210K") + || model.equals("SHV-E210L") + || model.equals("SHV-E210S") + || model.equals("SHW-M440S") + || model.equals("SPH-L710") + || model.equals("SPH-L710T")))) { + return "Galaxy S3"; + } + if ((codename != null && (codename.equals("golden") + || codename.equals("goldenlteatt") + || codename.equals("goldenltebmc") + || codename.equals("goldenltevzw") + || codename.equals("goldenve3g"))) + || (model != null && (model.equals("GT-I8190") + || model.equals("GT-I8190L") + || model.equals("GT-I8190N") + || model.equals("GT-I8190T") + || model.equals("GT-I8200L") + || model.equals("SAMSUNG-SM-G730A") + || model.equals("SM-G730V") + || model.equals("SM-G730W8")))) { + return "Galaxy S3 Mini"; + } + if ((codename != null && (codename.equals("goldenve3g") + || codename.equals("goldenvess3g"))) + || (model != null && (model.equals("GT-I8200") + || model.equals("GT-I8200N") + || model.equals("GT-I8200Q")))) { + return "Galaxy S3 Mini Value Edition"; + } + if ((codename != null && (codename.equals("s3ve3g") + || codename.equals("s3ve3gdd") + || codename.equals("s3ve3gds") + || codename.equals("s3ve3gdsdd"))) + || (model != null && (model.equals("GT-I9300I") + || model.equals("GT-I9301I") + || model.equals("GT-I9301Q")))) { + return "Galaxy S3 Neo"; + } + if ((codename != null && (codename.equals("SC-04E") + || codename.equals("ja3g") + || codename.equals("ja3gduosctc") + || codename.equals("jaltektt") + || codename.equals("jaltelgt") + || codename.equals("jalteskt") + || codename.equals("jflte") + || codename.equals("jflteMetroPCS") + || codename.equals("jflteaio") + || codename.equals("jflteatt") + || codename.equals("jfltecan") + || codename.equals("jfltecri") + || codename.equals("jfltecsp") + || codename.equals("jfltelra") + || codename.equals("jflterefreshspr") + || codename.equals("jfltespr") + || codename.equals("jfltetfnatt") + || codename.equals("jfltetfntmo") + || codename.equals("jfltetmo") + || codename.equals("jflteusc") + || codename.equals("jfltevzw") + || codename.equals("jfltevzwpp") + || codename.equals("jftdd") + || codename.equals("jfvelte") + || codename.equals("jfwifi") + || codename.equals("jsglte") + || codename.equals("ks01lte") + || codename.equals("ks01ltektt") + || codename.equals("ks01ltelgt"))) + || (model != null && (model.equals("GT-I9500") + || model.equals("GT-I9505") + || model.equals("GT-I9505X") + || model.equals("GT-I9506") + || model.equals("GT-I9507") + || model.equals("GT-I9507V") + || model.equals("GT-I9508") + || model.equals("GT-I9508C") + || model.equals("GT-I9508V") + || model.equals("GT-I9515") + || model.equals("GT-I9515L") + || model.equals("SAMSUNG-SGH-I337") + || model.equals("SAMSUNG-SGH-I337Z") + || model.equals("SC-04E") + || model.equals("SCH-I545") + || model.equals("SCH-I545L") + || model.equals("SCH-I545PP") + || model.equals("SCH-I959") + || model.equals("SCH-R970") + || model.equals("SCH-R970C") + || model.equals("SCH-R970X") + || model.equals("SGH-I337M") + || model.equals("SGH-M919") + || model.equals("SGH-M919N") + || model.equals("SGH-M919V") + || model.equals("SGH-S970G") + || model.equals("SHV-E300K") + || model.equals("SHV-E300L") + || model.equals("SHV-E300S") + || model.equals("SHV-E330K") + || model.equals("SHV-E330L") + || model.equals("SM-S975L") + || model.equals("SPH-L720") + || model.equals("SPH-L720T")))) { + return "Galaxy S4"; + } + if ((codename != null && (codename.equals("serrano3g") + || codename.equals("serranods") + || codename.equals("serranolte") + || codename.equals("serranoltebmc") + || codename.equals("serranoltektt") + || codename.equals("serranoltekx") + || codename.equals("serranoltelra") + || codename.equals("serranoltespr") + || codename.equals("serranolteusc") + || codename.equals("serranoltevzw") + || codename.equals("serranove3g") + || codename.equals("serranovelte") + || codename.equals("serranovolteatt"))) + || (model != null && (model.equals("GT-I9190") + || model.equals("GT-I9192") + || model.equals("GT-I9192I") + || model.equals("GT-I9195") + || model.equals("GT-I9195I") + || model.equals("GT-I9195L") + || model.equals("GT-I9195T") + || model.equals("GT-I9195X") + || model.equals("GT-I9197") + || model.equals("SAMSUNG-SGH-I257") + || model.equals("SCH-I435") + || model.equals("SCH-I435L") + || model.equals("SCH-R890") + || model.equals("SGH-I257M") + || model.equals("SHV-E370D") + || model.equals("SHV-E370K") + || model.equals("SPH-L520")))) { + return "Galaxy S4 Mini"; + } + if ((codename != null && (codename.equals("SC-04F") + || codename.equals("SCL23") + || codename.equals("k3g") + || codename.equals("klte") + || codename.equals("klteMetroPCS") + || codename.equals("klteacg") + || codename.equals("klteaio") + || codename.equals("klteatt") + || codename.equals("kltecan") + || codename.equals("klteduoszn") + || codename.equals("kltektt") + || codename.equals("kltelgt") + || codename.equals("kltelra") + || codename.equals("klteskt") + || codename.equals("kltespr") + || codename.equals("kltetfnvzw") + || codename.equals("kltetmo") + || codename.equals("klteusc") + || codename.equals("kltevzw") + || codename.equals("kwifi") + || codename.equals("lentisltektt") + || codename.equals("lentisltelgt") + || codename.equals("lentislteskt"))) + || (model != null && (model.equals("SAMSUNG-SM-G900A") + || model.equals("SAMSUNG-SM-G900AZ") + || model.equals("SC-04F") + || model.equals("SCL23") + || model.equals("SM-G9006W") + || model.equals("SM-G9008W") + || model.equals("SM-G9009W") + || model.equals("SM-G900F") + || model.equals("SM-G900FQ") + || model.equals("SM-G900H") + || model.equals("SM-G900I") + || model.equals("SM-G900K") + || model.equals("SM-G900L") + || model.equals("SM-G900M") + || model.equals("SM-G900MD") + || model.equals("SM-G900P") + || model.equals("SM-G900R4") + || model.equals("SM-G900R6") + || model.equals("SM-G900R7") + || model.equals("SM-G900S") + || model.equals("SM-G900T") + || model.equals("SM-G900T1") + || model.equals("SM-G900T3") + || model.equals("SM-G900T4") + || model.equals("SM-G900V") + || model.equals("SM-G900W8") + || model.equals("SM-G900X") + || model.equals("SM-G906K") + || model.equals("SM-G906L") + || model.equals("SM-G906S") + || model.equals("SM-S903VL")))) { + return "Galaxy S5"; + } + if ((codename != null && (codename.equals("s5neolte") + || codename.equals("s5neoltecan"))) + || (model != null && (model.equals("SM-G903F") + || model.equals("SM-G903M") + || model.equals("SM-G903W")))) { + return "Galaxy S5 Neo"; + } + if ((codename != null && (codename.equals("SC-05G") + || codename.equals("zeroflte") + || codename.equals("zeroflteacg") + || codename.equals("zeroflteaio") + || codename.equals("zeroflteatt") + || codename.equals("zerofltebmc") + || codename.equals("zerofltechn") + || codename.equals("zerofltectc") + || codename.equals("zerofltektt") + || codename.equals("zerofltelgt") + || codename.equals("zerofltelra") + || codename.equals("zerofltemtr") + || codename.equals("zeroflteskt") + || codename.equals("zerofltespr") + || codename.equals("zerofltetfnvzw") + || codename.equals("zerofltetmo") + || codename.equals("zeroflteusc") + || codename.equals("zerofltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G920A") + || model.equals("SAMSUNG-SM-G920AZ") + || model.equals("SC-05G") + || model.equals("SM-G9200") + || model.equals("SM-G9208") + || model.equals("SM-G9209") + || model.equals("SM-G920F") + || model.equals("SM-G920I") + || model.equals("SM-G920K") + || model.equals("SM-G920L") + || model.equals("SM-G920P") + || model.equals("SM-G920R4") + || model.equals("SM-G920R6") + || model.equals("SM-G920R7") + || model.equals("SM-G920S") + || model.equals("SM-G920T") + || model.equals("SM-G920T1") + || model.equals("SM-G920V") + || model.equals("SM-G920W8") + || model.equals("SM-G920X") + || model.equals("SM-S906L") + || model.equals("SM-S907VL")))) { + return "Galaxy S6"; + } + if ((codename != null && (codename.equals("404SC") + || codename.equals("SC-04G") + || codename.equals("SCV31") + || codename.equals("zerolte") + || codename.equals("zerolteacg") + || codename.equals("zerolteatt") + || codename.equals("zeroltebmc") + || codename.equals("zeroltechn") + || codename.equals("zeroltektt") + || codename.equals("zeroltelgt") + || codename.equals("zeroltelra") + || codename.equals("zerolteskt") + || codename.equals("zeroltespr") + || codename.equals("zeroltetmo") + || codename.equals("zerolteusc") + || codename.equals("zeroltevzw"))) + || (model != null && (model.equals("404SC") + || model.equals("SAMSUNG-SM-G925A") + || model.equals("SC-04G") + || model.equals("SCV31") + || model.equals("SM-G9250") + || model.equals("SM-G925F") + || model.equals("SM-G925I") + || model.equals("SM-G925K") + || model.equals("SM-G925L") + || model.equals("SM-G925P") + || model.equals("SM-G925R4") + || model.equals("SM-G925R6") + || model.equals("SM-G925R7") + || model.equals("SM-G925S") + || model.equals("SM-G925T") + || model.equals("SM-G925V") + || model.equals("SM-G925W8") + || model.equals("SM-G925X")))) { + return "Galaxy S6 Edge"; + } + if ((codename != null && (codename.equals("zenlte") + || codename.equals("zenlteatt") + || codename.equals("zenltebmc") + || codename.equals("zenltechn") + || codename.equals("zenltektt") + || codename.equals("zenltekx") + || codename.equals("zenltelgt") + || codename.equals("zenlteskt") + || codename.equals("zenltespr") + || codename.equals("zenltetmo") + || codename.equals("zenlteusc") + || codename.equals("zenltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G928A") + || model.equals("SM-G9280") + || model.equals("SM-G9287") + || model.equals("SM-G9287C") + || model.equals("SM-G928C") + || model.equals("SM-G928F") + || model.equals("SM-G928G") + || model.equals("SM-G928I") + || model.equals("SM-G928K") + || model.equals("SM-G928L") + || model.equals("SM-G928N0") + || model.equals("SM-G928P") + || model.equals("SM-G928R4") + || model.equals("SM-G928S") + || model.equals("SM-G928T") + || model.equals("SM-G928V") + || model.equals("SM-G928W8") + || model.equals("SM-G928X")))) { + return "Galaxy S6 Edge+"; + } + if ((codename != null && (codename.equals("herolte") + || codename.equals("heroltebmc") + || codename.equals("heroltektt") + || codename.equals("heroltelgt") + || codename.equals("herolteskt") + || codename.equals("heroqlteacg") + || codename.equals("heroqlteaio") + || codename.equals("heroqlteatt") + || codename.equals("heroqltecctvzw") + || codename.equals("heroqltechn") + || codename.equals("heroqltelra") + || codename.equals("heroqltemtr") + || codename.equals("heroqltespr") + || codename.equals("heroqltetfnvzw") + || codename.equals("heroqltetmo") + || codename.equals("heroqlteue") + || codename.equals("heroqlteusc") + || codename.equals("heroqltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G930A") + || model.equals("SAMSUNG-SM-G930AZ") + || model.equals("SM-G9300") + || model.equals("SM-G9308") + || model.equals("SM-G930F") + || model.equals("SM-G930K") + || model.equals("SM-G930L") + || model.equals("SM-G930P") + || model.equals("SM-G930R4") + || model.equals("SM-G930R6") + || model.equals("SM-G930R7") + || model.equals("SM-G930S") + || model.equals("SM-G930T") + || model.equals("SM-G930T1") + || model.equals("SM-G930U") + || model.equals("SM-G930V") + || model.equals("SM-G930VC") + || model.equals("SM-G930VL") + || model.equals("SM-G930W8") + || model.equals("SM-G930X")))) { + return "Galaxy S7"; + } + if ((codename != null && (codename.equals("SC-02H") + || codename.equals("SCV33") + || codename.equals("hero2lte") + || codename.equals("hero2ltebmc") + || codename.equals("hero2ltektt") + || codename.equals("hero2ltelgt") + || codename.equals("hero2lteskt") + || codename.equals("hero2qlteatt") + || codename.equals("hero2qltecctvzw") + || codename.equals("hero2qltechn") + || codename.equals("hero2qltespr") + || codename.equals("hero2qltetmo") + || codename.equals("hero2qlteue") + || codename.equals("hero2qlteusc") + || codename.equals("hero2qltevzw"))) + || (model != null && (model.equals("SAMSUNG-SM-G935A") + || model.equals("SC-02H") + || model.equals("SCV33") + || model.equals("SM-G9350") + || model.equals("SM-G935F") + || model.equals("SM-G935K") + || model.equals("SM-G935L") + || model.equals("SM-G935P") + || model.equals("SM-G935R4") + || model.equals("SM-G935S") + || model.equals("SM-G935T") + || model.equals("SM-G935U") + || model.equals("SM-G935V") + || model.equals("SM-G935VC") + || model.equals("SM-G935W8") + || model.equals("SM-G935X")))) { + return "Galaxy S7 Edge"; + } + if ((codename != null && (codename.equals("SC-02J") + || codename.equals("SCV36") + || codename.equals("dreamlte") + || codename.equals("dreamlteks") + || codename.equals("dreamqltecan") + || codename.equals("dreamqltechn") + || codename.equals("dreamqltecmcc") + || codename.equals("dreamqltesq") + || codename.equals("dreamqlteue"))) + || (model != null && (model.equals("SC-02J") + || model.equals("SCV36") + || model.equals("SM-G9500") + || model.equals("SM-G9508") + || model.equals("SM-G950F") + || model.equals("SM-G950N") + || model.equals("SM-G950U") + || model.equals("SM-G950U1") + || model.equals("SM-G950W")))) { + return "Galaxy S8"; + } + if ((codename != null && (codename.equals("SC-03J") + || codename.equals("SCV35") + || codename.equals("dream2lte") + || codename.equals("dream2lteks") + || codename.equals("dream2qltecan") + || codename.equals("dream2qltechn") + || codename.equals("dream2qltesq") + || codename.equals("dream2qlteue"))) + || (model != null && (model.equals("SC-03J") + || model.equals("SCV35") + || model.equals("SM-G9550") + || model.equals("SM-G955F") + || model.equals("SM-G955N") + || model.equals("SM-G955U") + || model.equals("SM-G955U1") + || model.equals("SM-G955W")))) { + return "Galaxy S8+"; + } + if ((codename != null && (codename.equals("starlte") + || codename.equals("starlteks") + || codename.equals("starqltechn") + || codename.equals("starqltecmcc") + || codename.equals("starqltecs") + || codename.equals("starqltesq") + || codename.equals("starqlteue"))) + || (model != null && (model.equals("SM-G9600") + || model.equals("SM-G9608") + || model.equals("SM-G960F") + || model.equals("SM-G960N") + || model.equals("SM-G960U") + || model.equals("SM-G960U1") + || model.equals("SM-G960W")))) { + return "Galaxy S9"; + } + if ((codename != null && (codename.equals("star2lte") + || codename.equals("star2lteks") + || codename.equals("star2qltechn") + || codename.equals("star2qltecs") + || codename.equals("star2qltesq") + || codename.equals("star2qlteue"))) + || (model != null && (model.equals("SM-G9650") + || model.equals("SM-G965F") + || model.equals("SM-G965N") + || model.equals("SM-G965U") + || model.equals("SM-G965U1") + || model.equals("SM-G965W")))) { + return "Galaxy S9+"; + } + if ((codename != null && (codename.equals("GT-P7500") + || codename.equals("GT-P7500D") + || codename.equals("GT-P7503") + || codename.equals("GT-P7510") + || codename.equals("SC-01D") + || codename.equals("SCH-I905") + || codename.equals("SGH-T859") + || codename.equals("SHW-M300W") + || codename.equals("SHW-M380K") + || codename.equals("SHW-M380S") + || codename.equals("SHW-M380W"))) + || (model != null && (model.equals("GT-P7500") + || model.equals("GT-P7500D") + || model.equals("GT-P7503") + || model.equals("GT-P7510") + || model.equals("SC-01D") + || model.equals("SCH-I905") + || model.equals("SGH-T859") + || model.equals("SHW-M300W") + || model.equals("SHW-M380K") + || model.equals("SHW-M380S") + || model.equals("SHW-M380W")))) { + return "Galaxy Tab 10.1"; + } + if ((codename != null && (codename.equals("GT-P6200") + || codename.equals("GT-P6200L") + || codename.equals("GT-P6201") + || codename.equals("GT-P6210") + || codename.equals("GT-P6211") + || codename.equals("SC-02D") + || codename.equals("SGH-T869") + || codename.equals("SHW-M430W"))) + || (model != null && (model.equals("GT-P6200") + || model.equals("GT-P6200L") + || model.equals("GT-P6201") + || model.equals("GT-P6210") + || model.equals("GT-P6211") + || model.equals("SC-02D") + || model.equals("SGH-T869") + || model.equals("SHW-M430W")))) { + return "Galaxy Tab 7.0 Plus"; + } + if ((codename != null && (codename.equals("gteslteatt") + || codename.equals("gtesltebmc") + || codename.equals("gtesltelgt") + || codename.equals("gteslteskt") + || codename.equals("gtesltetmo") + || codename.equals("gtesltetw") + || codename.equals("gtesltevzw") + || codename.equals("gtesqltespr") + || codename.equals("gtesqlteusc"))) + || (model != null && (model.equals("SAMSUNG-SM-T377A") + || model.equals("SM-T375L") + || model.equals("SM-T375S") + || model.equals("SM-T3777") + || model.equals("SM-T377P") + || model.equals("SM-T377R4") + || model.equals("SM-T377T") + || model.equals("SM-T377V") + || model.equals("SM-T377W")))) { + return "Galaxy Tab E 8.0"; + } + if ((codename != null && (codename.equals("gtel3g") + || codename.equals("gtelltevzw") + || codename.equals("gtelwifi") + || codename.equals("gtelwifichn") + || codename.equals("gtelwifiue"))) + || (model != null && (model.equals("SM-T560") + || model.equals("SM-T560NU") + || model.equals("SM-T561") + || model.equals("SM-T561M") + || model.equals("SM-T561Y") + || model.equals("SM-T562") + || model.equals("SM-T567V")))) { + return "Galaxy Tab E 9.6"; + } + if ((codename != null && (codename.equals("403SC") + || codename.equals("degas2wifi") + || codename.equals("degas2wifibmwchn") + || codename.equals("degas3g") + || codename.equals("degaslte") + || codename.equals("degasltespr") + || codename.equals("degasltevzw") + || codename.equals("degasvelte") + || codename.equals("degasveltechn") + || codename.equals("degaswifi") + || codename.equals("degaswifibmwzc") + || codename.equals("degaswifidtv") + || codename.equals("degaswifiopenbnn") + || codename.equals("degaswifiue"))) + || (model != null && (model.equals("403SC") + || model.equals("SM-T230") + || model.equals("SM-T230NT") + || model.equals("SM-T230NU") + || model.equals("SM-T230NW") + || model.equals("SM-T230NY") + || model.equals("SM-T230X") + || model.equals("SM-T231") + || model.equals("SM-T232") + || model.equals("SM-T235") + || model.equals("SM-T235Y") + || model.equals("SM-T237P") + || model.equals("SM-T237V") + || model.equals("SM-T239") + || model.equals("SM-T2397") + || model.equals("SM-T239C") + || model.equals("SM-T239M")))) { + return "Galaxy Tab4 7.0"; + } + if ((codename != null && (codename.equals("gvlte") + || codename.equals("gvlteatt") + || codename.equals("gvltevzw") + || codename.equals("gvltexsp") + || codename.equals("gvwifijpn") + || codename.equals("gvwifiue"))) + || (model != null && (model.equals("SAMSUNG-SM-T677A") + || model.equals("SM-T670") + || model.equals("SM-T677") + || model.equals("SM-T677V")))) { + return "Galaxy View"; + } + if ((codename != null && codename.equals("manta"))) { + return "Nexus 10"; + } + // ---------------------------------------------------------------------------- + // Sony + if ((codename != null && (codename.equals("D2104") + || codename.equals("D2105"))) + || (model != null && (model.equals("D2104") + || model.equals("D2105")))) { + return "Xperia E1 dual"; + } + if ((codename != null && (codename.equals("D2202") + || codename.equals("D2203") + || codename.equals("D2206") + || codename.equals("D2243"))) + || (model != null && (model.equals("D2202") + || model.equals("D2203") + || model.equals("D2206") + || model.equals("D2243")))) { + return "Xperia E3"; + } + if ((codename != null && (codename.equals("E5603") + || codename.equals("E5606") + || codename.equals("E5653"))) + || (model != null && (model.equals("E5603") + || model.equals("E5606") + || model.equals("E5653")))) { + return "Xperia M5"; + } + if ((codename != null && (codename.equals("E5633") + || codename.equals("E5643") + || codename.equals("E5663"))) + || (model != null && (model.equals("E5633") + || model.equals("E5643") + || model.equals("E5663")))) { + return "Xperia M5 Dual"; + } + if ((codename != null && codename.equals("LT26i")) + || (model != null && model.equals("LT26i"))) { + return "Xperia S"; + } + if ((codename != null && (codename.equals("D5303") + || codename.equals("D5306") + || codename.equals("D5316") + || codename.equals("D5316N") + || codename.equals("D5322"))) + || (model != null && (model.equals("D5303") + || model.equals("D5306") + || model.equals("D5316") + || model.equals("D5316N") + || model.equals("D5322")))) { + return "Xperia T2 Ultra"; + } + if ((codename != null && (codename.equals("txs03"))) + || (model != null && (model.equals("SGPT12") + || model.equals("SGPT13")))) { + return "Xperia Tablet S"; + } + if ((codename != null && (codename.equals("SGP311") + || codename.equals("SGP312") + || codename.equals("SGP321") + || codename.equals("SGP351"))) + || (model != null && (model.equals("SGP311") + || model.equals("SGP312") + || model.equals("SGP321") + || model.equals("SGP351")))) { + return "Xperia Tablet Z"; + } + if ((codename != null && (codename.equals("D6502") + || codename.equals("D6503") + || codename.equals("D6543") + || codename.equals("SO-03F"))) + || (model != null && (model.equals("D6502") + || model.equals("D6503") + || model.equals("D6543") + || model.equals("SO-03F")))) { + return "Xperia Z2"; + } + if ((codename != null && (codename.equals("401SO") + || codename.equals("D6603") + || codename.equals("D6616") + || codename.equals("D6643") + || codename.equals("D6646") + || codename.equals("D6653") + || codename.equals("SO-01G") + || codename.equals("SOL26") + || codename.equals("leo"))) + || (model != null && (model.equals("401SO") + || model.equals("D6603") + || model.equals("D6616") + || model.equals("D6643") + || model.equals("D6646") + || model.equals("D6653") + || model.equals("SO-01G") + || model.equals("SOL26")))) { + return "Xperia Z3"; + } + if ((codename != null && (codename.equals("402SO") + || codename.equals("SO-03G") + || codename.equals("SOV31"))) + || (model != null && (model.equals("402SO") + || model.equals("SO-03G") + || model.equals("SOV31")))) { + return "Xperia Z4"; + } + if ((codename != null && (codename.equals("E5803") + || codename.equals("E5823") + || codename.equals("SO-02H"))) + || (model != null && (model.equals("E5803") + || model.equals("E5823") + || model.equals("SO-02H")))) { + return "Xperia Z5 Compact"; + } + // ---------------------------------------------------------------------------- + // Sony Ericsson + if ((codename != null && (codename.equals("LT26i") + || codename.equals("SO-02D"))) + || (model != null && (model.equals("LT26i") + || model.equals("SO-02D")))) { + return "Xperia S"; + } + if ((codename != null && (codename.equals("SGP311") + || codename.equals("SGP321") + || codename.equals("SGP341") + || codename.equals("SO-03E"))) + || (model != null && (model.equals("SGP311") + || model.equals("SGP321") + || model.equals("SGP341") + || model.equals("SO-03E")))) { + return "Xperia Tablet Z"; + } + return fallback; + } + + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context + * the application context. + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context) { + return getDeviceInfo(context.getApplicationContext(), Build.DEVICE, Build.MODEL); + } + + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context + * the application context. + * @param codename + * the codename of the device + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context, String codename) { + return getDeviceInfo(context, codename, null); + } + + /** + * Get the {@link DeviceInfo} for the current device. Do not run on the UI thread, as this may + * download JSON to retrieve the {@link DeviceInfo}. JSON is only downloaded once and then + * stored to {@link SharedPreferences}. + * + * @param context + * the application context. + * @param codename + * the codename of the device + * @param model + * the model of the device + * @return {@link DeviceInfo} for the current device. + */ + @WorkerThread + public static DeviceInfo getDeviceInfo(Context context, String codename, String model) { + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + String key = String.format("%s:%s", codename, model); + String savedJson = prefs.getString(key, null); + if (savedJson != null) { + try { + return new DeviceInfo(new JSONObject(savedJson)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + // check if we have an internet connection + int ret = context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE); + boolean isConnectedToNetwork = false; + if (ret == PackageManager.PERMISSION_GRANTED) { + ConnectivityManager connMgr = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + isConnectedToNetwork = true; + } + } else { + // assume we are connected. + isConnectedToNetwork = true; + } + + if (isConnectedToNetwork) { + try { + // Get the device name from the generated JSON files created from Google's device list. + String url = String.format(DEVICE_JSON_URL, codename.toLowerCase(Locale.ENGLISH)); + String jsonString = downloadJson(url); + JSONArray jsonArray = new JSONArray(jsonString); + for (int i = 0, len = jsonArray.length(); i < len; i++) { + JSONObject json = jsonArray.getJSONObject(i); + DeviceInfo info = new DeviceInfo(json); + if ((codename.equalsIgnoreCase(info.codename) && model == null) + || codename.equalsIgnoreCase(info.codename) && model.equalsIgnoreCase(info.model)) { + // Save to SharedPreferences so we don't need to make another request. + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(key, json.toString()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + editor.apply(); + } else { + editor.commit(); + } + return info; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (codename.equals(Build.DEVICE) && model.equals(Build.MODEL)) { + return new DeviceInfo(Build.MANUFACTURER, getDeviceName(), codename, model); // current device + } + + return new DeviceInfo(null, null, codename, model); // unknown device + } + + /** + *

Capitalizes getAllProcesses the whitespace separated words in a String. Only the first + * letter of each word is changed.

+ * + * Whitespace is defined by {@link Character#isWhitespace(char)}. + * + * @param str + * the String to capitalize + * @return capitalized The capitalized String + */ + private static String capitalize(String str) { + if (TextUtils.isEmpty(str)) { + return str; + } + char[] arr = str.toCharArray(); + boolean capitalizeNext = true; + String phrase = ""; + for (char c : arr) { + if (capitalizeNext && Character.isLetter(c)) { + phrase += Character.toUpperCase(c); + capitalizeNext = false; + continue; + } else if (Character.isWhitespace(c)) { + capitalizeNext = true; + } + phrase += c; + } + return phrase; + } + + /** Download URL to String */ + @WorkerThread + private static String downloadJson(String myurl) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try { + URL url = new URL(myurl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setReadTimeout(10000); + conn.setConnectTimeout(15000); + conn.setRequestMethod("GET"); + conn.setDoInput(true); + conn.connect(); + if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { + reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append('\n'); + } + } + return sb.toString(); + } finally { + if (reader != null) { + reader.close(); + } + } + } + + public static final class Request { + + final Context context; + final Handler handler; + String codename; + String model; + + private Request(Context ctx) { + context = ctx; + handler = new Handler(ctx.getMainLooper()); + } + + /** + * Set the device codename to query. You should also set the model. + * + * @param codename + * the value of the system property "ro.product.device" + * @return This Request object to allow for chaining of calls to set methods. + * @see Build#DEVICE + */ + public Request setCodename(String codename) { + this.codename = codename; + return this; + } + + /** + * Set the device model to query. You should also set the codename. + * + * @param model + * the value of the system property "ro.product.model" + * @return This Request object to allow for chaining of calls to set methods. + * @see Build#MODEL + */ + public Request setModel(String model) { + this.model = model; + return this; + } + + /** + * Download information about the device. This saves the results in shared-preferences so + * future requests will not need a network connection. + * + * @param callback + * the callback to retrieve the {@link DeviceInfo} + */ + public void request(Callback callback) { + if (codename == null && model == null) { + codename = Build.DEVICE; + model = Build.MODEL; + } + GetDeviceRunnable runnable = new GetDeviceRunnable(callback); + if (Looper.myLooper() == Looper.getMainLooper()) { + new Thread(runnable).start(); + } else { + runnable.run(); // already running in background thread. + } + } + + private final class GetDeviceRunnable implements Runnable { + + final Callback callback; + DeviceInfo deviceInfo; + Exception error; + + public GetDeviceRunnable(Callback callback) { + this.callback = callback; + } + + @Override public void run() { + try { + deviceInfo = getDeviceInfo(context, codename, model); + } catch (Exception e) { + error = e; + } + handler.post(new Runnable() { + + @Override public void run() { + callback.onFinished(deviceInfo, error); + } + }); + } + } + + } + + /** + * Callback which is invoked when the {@link DeviceInfo} is finished loading. + */ + public interface Callback { + + /** + * Callback to get the device info. This is run on the UI thread. + * + * @param info + * the requested {@link DeviceInfo} + * @param error + * {@code null} if nothing went wrong. + */ + void onFinished(DeviceInfo info, Exception error); + } + + /** + * Device information based on + * Google's maintained list. + */ + public static final class DeviceInfo { + + /** Retail branding */ + public final String manufacturer; + + /** Marketing name */ + public final String marketName; + + /** the value of the system property "ro.product.device" */ + public final String codename; + + /** the value of the system property "ro.product.model" */ + public final String model; + + public DeviceInfo(String manufacturer, String marketName, String codename, String model) { + this.manufacturer = manufacturer; + this.marketName = marketName; + this.codename = codename; + this.model = model; + } + + private DeviceInfo(JSONObject jsonObject) throws JSONException { + manufacturer = jsonObject.getString("manufacturer"); + marketName = jsonObject.getString("market_name"); + codename = jsonObject.getString("codename"); + model = jsonObject.getString("model"); + } + + /** + * @return the consumer friendly name of the device. + */ + public String getName() { + if (!TextUtils.isEmpty(marketName)) { + return marketName; + } + return capitalize(model); + } + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/Digest.java b/app/src/main/java/com/tangem/wallet/Digest.java new file mode 100644 index 0000000000..b3924d61c7 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Digest.java @@ -0,0 +1,114 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 18.12.2017. + */ + +public interface Digest{ + + /** + * Insert one more input data byte. + * + * @param in the input byte + */ + void update(byte in); + + /** + * Insert some more bytes. + * + * @param inbuf the data bytes + */ + void update(byte[] inbuf); + + /** + * Insert some more bytes. + * + * @param inbuf the data buffer + * @param off the data offset in {@code inbuf} + * @param len the data length (in bytes) + */ + void update(byte[] inbuf, int off, int len); + + /** + * Finalize the current hash computation and return the hash value + * in a newly-allocated array. The object is resetted. + * + * @return the hash output + */ + byte[] digest(); + + /** + * Input some bytes, then finalize the current hash computation + * and return the hash value in a newly-allocated array. The object + * is resetted. + * + * @param inbuf the input data + * @return the hash output + */ + byte[] digest(byte[] inbuf); + + /** + * Finalize the current hash computation and store the hash value + * in the provided output buffer. The {@code len} parameter + * contains the maximum number of bytes that should be written; + * no more bytes than the natural hash function output length will + * be produced. If {@code len} is smaller than the natural + * hash output length, the hash output is truncated to its first + * {@code len} bytes. The object is resetted. + * + * @param outbuf the output buffer + * @param off the output offset within {@code outbuf} + * @param len the requested hash output length (in bytes) + * @return the number of bytes actually written in {@code outbuf} + */ + int digest(byte[] outbuf, int off, int len); + + /** + * Get the natural hash function output length (in bytes). + * + * @return the digest output length (in bytes) + */ + int getDigestLength(); + + /** + * Reset the object: this makes it suitable for a new hash + * computation. The current computation, if any, is discarded. + */ + void reset(); + + /** + * Clone the current state. The returned object evolves independantly + * of this object. + * + * @return the clone + */ + Digest copy(); + + /** + *

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 + * HMAC specification + * names the "{@code B}" parameter).

+ * + *

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.

+ * + * @return the internal block length (in bytes), or {@code -n} + */ + int getBlockLength(); + + /** + *

Get the display name for this function (e.g. {@code "SHA-1"} + * for SHA-1).

+ * + * @see Object + */ + String toString(); +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/DigestEngine.java b/app/src/main/java/com/tangem/wallet/DigestEngine.java new file mode 100644 index 0000000000..8dd130353d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/DigestEngine.java @@ -0,0 +1,220 @@ +package com.tangem.wallet; + + +import java.security.MessageDigest; + +/** + * Created by Ilia on 18.12.2017. + */ + +public abstract class DigestEngine extends MessageDigest implements Digest { + + /** + * Reset the hash algorithm state. + */ + protected abstract void engineReset(); + + /** + * Process one block of data. + * + * @param data the data block + */ + protected abstract void processBlock(byte[] data); + + /** + * Perform the final padding and store the result in the + * provided buffer. This method shall call {@link #flush} + * and then {@link #update} with the appropriate padding + * data in order to get the full input data. + * + * @param buf the output buffer + * @param off the output offset + */ + protected abstract void doPadding(byte[] buf, int off); + + /** + * This function is called at object creation time; the + * implementation should use it to perform initialization tasks. + * After this method is called, the implementation should be ready + * to process data or meaningfully honour calls such as + * {@link #engineGetDigestLength} + */ + protected abstract void doInit(); + + private int digestLen, blockLen, inputLen; + private byte[] inputBuf, outputBuf; + private long blockCount; + + /** + * Instantiate the engine. + */ + public DigestEngine(String alg) + { + super(alg); + doInit(); + digestLen = engineGetDigestLength(); + blockLen = getInternalBlockLength(); + inputBuf = new byte[blockLen]; + outputBuf = new byte[digestLen]; + inputLen = 0; + blockCount = 0; + } + + private void adjustDigestLen() + { + if (digestLen == 0) { + digestLen = engineGetDigestLength(); + outputBuf = new byte[digestLen]; + } + } + + public byte[] digest() + { + adjustDigestLen(); + byte[] result = new byte[digestLen]; + digest(result, 0, digestLen); + return result; + } + + public byte[] digest(byte[] input) + { + update(input, 0, input.length); + return digest(); + } + + public int digest(byte[] buf, int offset, int len) + { + adjustDigestLen(); + if (len >= digestLen) { + doPadding(buf, offset); + reset(); + return digestLen; + } else { + doPadding(outputBuf, 0); + System.arraycopy(outputBuf, 0, buf, offset, len); + reset(); + return len; + } + } + + public void reset() + { + engineReset(); + inputLen = 0; + blockCount = 0; + } + + public void update(byte input) + { + inputBuf[inputLen ++] = (byte)input; + if (inputLen == blockLen) { + processBlock(inputBuf); + blockCount ++; + inputLen = 0; + } + } + + public void update(byte[] input) + { + update(input, 0, input.length); + } + + public void update(byte[] input, int offset, int len) + { + while (len > 0) { + int copyLen = blockLen - inputLen; + if (copyLen > len) + copyLen = len; + System.arraycopy(input, offset, inputBuf, inputLen, + copyLen); + offset += copyLen; + inputLen += copyLen; + len -= copyLen; + if (inputLen == blockLen) { + processBlock(inputBuf); + blockCount ++; + inputLen = 0; + } + } + } + + /** + * Get the internal block length. This is the length (in + * bytes) of the array which will be passed as parameter to + * {@link #processBlock}. The default implementation of this + * method calls {@link #getBlockLength} and returns the same + * value. Overriding this method is useful when the advertised + * block length (which is used, for instance, by HMAC) is + * suboptimal with regards to internal buffering needs. + * + * @return the internal block length (in bytes) + */ + protected int getInternalBlockLength() + { + return getBlockLength(); + } + + /** + * Flush internal buffers, so that less than a block of data + * may at most be upheld. + * + * @return the number of bytes still unprocessed after the flush + */ + protected final int flush() + { + return inputLen; + } + + /** + * Get a reference to an internal buffer with the same size + * than a block. The contents of that buffer are defined only + * immediately after a call to {@link #flush()}: if + * {@link #flush()} return the value {@code n}, then the + * first {@code n} bytes of the array returned by this method + * are the {@code n} bytes of input data which are still + * unprocessed. The values of the remaining bytes are + * undefined and may be altered at will. + * + * @return a block-sized internal buffer + */ + protected final byte[] getBlockBuffer() + { + return inputBuf; + } + + /** + * Get the "block count": this is the number of times the + * {@link #processBlock} method has been invoked for the + * current hash operation. That counter is incremented + * after 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; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java b/app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java new file mode 100644 index 0000000000..98d2f3e47f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ECDSASignature_ETH.java @@ -0,0 +1,52 @@ +package com.tangem.wallet; + +import java.math.BigInteger; + +/** + * Created by Ilia on 07.01.2018. + */ + +public class ECDSASignature_ETH { + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the signature. + * + * @param r - + * @param s - + */ + public ECDSASignature_ETH(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + /** + *t + * @param r + * @param s + * @return - + */ + private static ECDSASignature_ETH fromComponents(byte[] r, byte[] s) { + return new ECDSASignature_ETH(new BigInteger(1, r), new BigInteger(1, s)); + } + + /** + * + * @param r - + * @param s - + * @param v - + * @return - + */ + public static ECDSASignature_ETH fromComponents(byte[] r, byte[] s, byte v) { + ECDSASignature_ETH signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + +} + diff --git a/app/src/main/java/com/tangem/wallet/ETH_Transaction.java b/app/src/main/java/com/tangem/wallet/ETH_Transaction.java new file mode 100644 index 0000000000..476bc096ba --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ETH_Transaction.java @@ -0,0 +1,229 @@ +package com.tangem.wallet; + + +import android.util.Log; + +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +import static com.tangem.wallet.ByteUtil.EMPTY_BYTE_ARRAY; + +/** + * Created by Ilia on 07.01.2018. + */ + +public class ETH_Transaction { + byte[] nonce; + byte[] gasPrice; + byte[] gasLimit; + byte[] receiveAddress; + byte[] value; + byte[] data; + Integer chainId; + byte[] rlpRaw; + public ECDSASignature_ETH signature; + byte[] rlpEncoded; + + private static final int CHAIN_ID_INC = 35; + private static final int LOWER_REAL_V = 27; + + public static ETH_Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, + BigInteger gasLimit, Integer chainId){ + return new ETH_Transaction(BigIntegers.asUnsignedByteArray(nonce), + BigIntegers.asUnsignedByteArray(gasPrice), + BigIntegers.asUnsignedByteArray(gasLimit), + Hex.decode(to), + BigIntegers.asUnsignedByteArray(amount), + null, + chainId); + } + + + public static ETH_Transaction create(String to, BigInteger amount, BigInteger nonce, BigInteger gasPrice, + BigInteger gasLimit, Integer chainId, byte[] data){ + return new ETH_Transaction(BigIntegers.asUnsignedByteArray(nonce), + BigIntegers.asUnsignedByteArray(gasPrice), + BigIntegers.asUnsignedByteArray(gasLimit), + Hex.decode(to), + BigIntegers.asUnsignedByteArray(amount), + data, + chainId); + } + + public ETH_Transaction(byte[] nonce, byte[] gasPrice, byte[] gasLimit, byte[] receiveAddress, byte[] value, byte[] data, + Integer chainId) { + this.nonce = nonce; + this.gasPrice = gasPrice; + this.gasLimit = gasLimit; + this.receiveAddress = receiveAddress; + if (ByteUtil.isSingleZero(value)) { + this.value = EMPTY_BYTE_ARRAY; + } else { + this.value = value; + } + this.data = data; + this.chainId = chainId; + + if (receiveAddress == null) { + this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY; + } + } + + public enum ChainEnum + { + Mainnet(1), + Morden(2), + Ropsten(3), + Rinkeby(4), + Rootstock_mainnet(30), + Rootstock_testnet (31), + Kovan (42), + Ethereum_Classic_mainnet(61), + Ethereum_Classic_testnet(62), + Geth_private_chains (1337); + + private int value; + + ChainEnum(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public byte[] getRawHash() { + + byte[] plainMsg = this.getEncodedRaw(); + Keccak256 kec = new Keccak256(); + return kec.digest(plainMsg); + } + + public byte[] getHash() { + + byte[] plainMsg = this.getEncoded(); + Keccak256 kec = new Keccak256(); + return kec.digest(plainMsg); + } + + public int BruteRecoveryID2(ECDSASignature_ETH sig, byte[] messageHash, byte[] thisKey) + { + Log.e("ETH_KZ", BTCUtils.toHex(thisKey)); + int recId = -1; + for (int i = 0; i < 4; i++) { + byte[] recK = CryptoUtil.recoverPubBytesFromSignature(i, sig, messageHash); + + if(recK == null) + { + continue; + } + + Log.e("ETH_k "+String.valueOf(i), BTCUtils.toHex(recK)); + if (Arrays.equals(recK, thisKey)) { + recId = i; + recId +=27; + break; + } + } + return recId; + } + + public int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey) + { + Log.e("ETH_KZ", BTCUtils.toHex(thisKey)); + int recId = -1; + for (int i = 0; i < 4; i++) { + ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, false); + + if(k == null) + continue; + byte[] recK = k.getPubKey(); + Log.e("ETH_k "+String.valueOf(i), BTCUtils.toHex(recK)); + if (k != null && Arrays.equals(recK, thisKey)) { + recId = i; + break; + } + } + return recId; + } + + // signed TX + public byte[] getEncoded() { + + // parse null as 0 for nonce + byte[] nonce = null; + if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) { + nonce = RLP.encodeElement(null); + } else { + nonce = RLP.encodeElement(this.nonce); + } + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] value = RLP.encodeElement(this.value); + byte[] data = RLP.encodeElement(this.data); + + byte[] v, r, s; + + if (signature != null) { + int encodeV; + if (chainId == null) { + encodeV = signature.v; + } else { + encodeV = signature.v - LOWER_REAL_V; + encodeV += chainId * 2 + CHAIN_ID_INC; + } + v = RLP.encodeInt(encodeV); + r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r)); + s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s)); + } else { + // Since EIP-155 use chainId for v + v = chainId == null ? RLP.encodeElement(EMPTY_BYTE_ARRAY) : RLP.encodeInt(chainId); + r = RLP.encodeElement(EMPTY_BYTE_ARRAY); + s = RLP.encodeElement(EMPTY_BYTE_ARRAY); + } + + this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, + receiveAddress, value, data, v, r, s); + + //this.hash = this.getHash(); + + return rlpEncoded; + } + + // unsigned TX + public byte[] getEncodedRaw() { + // parse null as 0 for nonce + byte[] nonce = null; + if (this.nonce == null || this.nonce.length == 1 && this.nonce[0] == 0) { + nonce = RLP.encodeElement(null); + } else { + nonce = RLP.encodeElement(this.nonce); + } + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] value = RLP.encodeElement(this.value); + byte[] data = RLP.encodeElement(this.data); + + // Since EIP-155 use chainId for v + if (chainId == null) { + rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, + value, data); + } else { + byte[] v, r, s; + v = RLP.encodeInt(chainId); + r = RLP.encodeElement(EMPTY_BYTE_ARRAY); + s = RLP.encodeElement(EMPTY_BYTE_ARRAY); + rlpRaw = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, + value, data, v, r, s); + } + return rlpRaw; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Electrum_Request.java b/app/src/main/java/com/tangem/wallet/Electrum_Request.java new file mode 100644 index 0000000000..ae5a34cbde --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Electrum_Request.java @@ -0,0 +1,184 @@ +package com.tangem.wallet; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Electrum_Request { + public static final String METHOD_GetBalance = "blockchain.address.get_balance"; + public static final String METHOD_ListUnspent = "blockchain.address.listunspent"; + public static final String METHOD_GetHistory = "blockchain.address.get_history"; + public static final String METHOD_GetTransaction = "blockchain.transaction.get"; + public static final String METHOD_GetHeader = "blockchain.block.get_header"; + public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast"; + public static final String METHOD_GetFee = "blockchain.estimatefee"; + + + + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public String TxHash; + public String Host; + public int Port; + + private Electrum_Request() { + } + + public Electrum_Request(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public String getAsString() { + return jsRequestData.toString(); + } + + public void setID(int value) { + try { + jsRequestData.put("id", String.format("%d", value)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public static Electrum_Request CheckBalance(String wallet) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetBalance + "\", \"params\":[\"" + wallet + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + public static Electrum_Request GetFee(String wallet) { + Electrum_Request request = new Electrum_Request(); + try{ + request.WalletAddress = wallet; //METHOD_GetFee + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetFee + "\", \"params\":[\"" + 6 + "\"] }"); + } + catch(JSONException e) + { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request GetHeader(String wallet, String height) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetHeader + "\", \"params\":[\"" + height + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request ListUnspent(String wallet) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ListUnspent + "\", \"params\":[\"" + wallet + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request Broadcast(String wallet, String tx) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_SendTransaction + "\", \"params\":[\"" + tx + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request ListHistory(String wallet) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetHistory + "\", \"params\":[\"" + wallet + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Electrum_Request GetTransaction(String wallet, String tx_hash) { + Electrum_Request request = new Electrum_Request(); + try { + request.WalletAddress=wallet; + request.TxHash = tx_hash; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetTransaction + "\", \"params\":[\"" + tx_hash + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public boolean isMethod(String methodName) throws JSONException { + return jsRequestData.getString("method").equals(methodName); + } + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/Electrum_Task.java b/app/src/main/java/com/tangem/wallet/Electrum_Task.java new file mode 100644 index 0000000000..ec9512a2a5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Electrum_Task.java @@ -0,0 +1,116 @@ +package com.tangem.wallet; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Electrum_Task extends AsyncTask> { + public static final String logTag = "Electrum"; + //public static final String Host = /*"hsmiths.changeip.net";*/ "testnetnode.arihanc.com"; + //public static final int Port = /*8080*/51001; + private int reqID = 1; + private String Host = ""; + private int Port = 0; + OutputStreamWriter out; + BufferedReader in; + + SharedData sharedCounter = null; + + + public Electrum_Task(String host, int port) { + super(); + Host = host; + Port = port; + } + + public Electrum_Task(String host, int port, SharedData sharedCounter) { + super(); + Host = host; + Port = port; + this.sharedCounter = sharedCounter; + } + + @Override + protected List doInBackground(Electrum_Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + try { + + InetAddress serverAddress = InetAddress.getByName(Host); + Log.v(logTag, "Connecting..."+Host); + Socket socket = new Socket(serverAddress, Port); + socket.setSoTimeout(5000); + try { + OutputStream os = socket.getOutputStream(); + out = new OutputStreamWriter(os, "UTF-8"); + Log.v(logTag, "Connected"); + InputStream is = socket.getInputStream(); + in = new BufferedReader(new InputStreamReader(is)); + + publishProgress(5); + + for (int i = 0; i < requests.length; i++) { + requests[i].setID(reqID++); + doRequest(requests[i]); + publishProgress(5 + 90 * (i + 1) / requests.length); + } + + publishProgress(100); + + } catch (Exception e) { + Log.e(logTag, "Error: ", e); + } finally { + socket.close(); + } + } catch (Exception e) { + Log.e(logTag, "Error: ", e); + for (int i = 0; i < requests.length; i++) { + result.get(i).error = e.toString(); + } + } + return result; + } + + private void doRequest(Electrum_Request request) { + try { + + Log.v(logTag, "<< " + request.getAsString()); + + out.write(request.getAsString() + "\n"); + out.flush(); + + request.answerData = in.readLine(); + request.Host=Host; + request.Port=Port; + if (request.answerData != null) { + Log.v(logTag, ">> " + request.answerData); + } else { + request.error = "No answer from server"; + Log.v(logTag, ">> "); + } + } catch (Exception e) { + request.error = e.toString(); + } + } + + public String getValidationNodeDescription() { + return "Electrum, "+Host+":"+String.valueOf(Port); + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java b/app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java new file mode 100644 index 0000000000..4413e2844b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EmptyWalletActivity.java @@ -0,0 +1,356 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class EmptyWalletActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + private static final int REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = 2; + private static final int REQUEST_CODE_REQUEST_PIN2 = 3; + private static final int REQUEST_CODE_VERIFY_CARD = 4; + Tangem_Card mCard; + TextView tvCardID, tvIssuer, tvIssuerData, tvBlockchain; + ProgressBar progressBar; + ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion; + + private NfcManager mNfcManager; + private final String logTag = "EmptyWalletActivity"; + private boolean lastReadSuccess = true; + private VerifyCardTask verifyCardTask = null; + private int requestPIN2Count = 0; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_empty_wallet); + + MainActivity.commonInit(getApplicationContext()); + mNfcManager = new NfcManager(this, this); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + tvCardID = findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + tvIssuer = findViewById(R.id.tvIssuer); + tvIssuerData = findViewById(R.id.tvIssuerData); + tvBlockchain = findViewById(R.id.tvBlockchain); + + tvIssuer.setText(mCard.getIssuerDescription()); + tvIssuerData.setText(mCard.getIssuerDataDescription()); + + //tvBlockchain.setText(mCard.getBlockchain().getOfficialName()); + tvBlockchain.setText(mCard.getBlockchainName()); + progressBar = findViewById(R.id.progressBar); + + ivBlockchain = findViewById(R.id.imgBlockchain); + ivPIN = findViewById(R.id.imgPIN); + ivPIN2orSecurityDelay = findViewById(R.id.imgPIN2orSecurityDelay); + ivDeveloperVersion = findViewById(R.id.imgDeveloperVersion); + + ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this, mCard.getTokenSymbol())); + + if (mCard.useDefaultPIN1()) { + ivPIN.setImageResource(R.drawable.unlock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN.setImageResource(R.drawable.lock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } + + if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.timer); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show(); + } + }); + + } else if (mCard.useDefaultPIN2()) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } + + + if (mCard.useDevelopersFirmware()) { + ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version); + ivDeveloperVersion.setVisibility(View.VISIBLE); + ivDeveloperVersion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(EmptyWalletActivity.this, "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivDeveloperVersion.setVisibility(View.INVISIBLE); + } + + Button btnNewWallet = findViewById(R.id.btnNewWallet); + btnNewWallet.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //CreateSelectBlockchainDialog(); + requestPIN2Count = 0; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + } + }); + + if (getIntent().getExtras().containsKey(NfcAdapter.EXTRA_TAG)) { + Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null) { + onTagDiscovered(tag); + } + } + } + + + private void doCreateNewWallet() { + Intent intent = new Intent(this, CreateNewWalletActivity.class); + + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + +// intent.putExtra("newPIN",mCard.getPIN()); +// intent.putExtra("newPIN2","12345678"); + startActivityForResult(intent, REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) { + if (resultCode == Activity.RESULT_OK) { + + if (data != null) { + data.putExtra("modification", "updateAndViewCard"); + data.putExtra("updateDelay", 0); + setResult(Activity.RESULT_OK, data); + } + finish(); + } else { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getBaseContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2); + return; + } + } + setResult(resultCode, data); + finish(); + } else if (requestCode == REQUEST_CODE_REQUEST_PIN2) { + if (resultCode == Activity.RESULT_OK) { + doCreateNewWallet(); + } + } + + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + if (!mCard.getUID().equals(sUID)) { + Log.d(logTag, "Invalid UID: " + sUID); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } else { + Log.v(logTag, "UID: " + sUID); + } + + if (lastReadSuccess) { + isoDep.setTimeout(1000); + } else { + isoDep.setTimeout(65000); + } + //lastTag = tag; + verifyCardTask = new VerifyCardTask(this, mCard, mNfcManager, isoDep, this); + verifyCardTask.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + verifyCardTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(EmptyWalletActivity.this, VerifyCardActivity.class); + // TODO обновить карту mCard + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_VERIFY_CARD); + //addCard(cardProtocol.getCard()); + } + }); + } else { + // remove last UIDs because of error and no card read + progressBar.post(new Runnable() { + @Override + public void run() { + lastReadSuccess = false; + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(EmptyWalletActivity.this, "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + verifyCardTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } + + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/EthEngine.java b/app/src/main/java/com/tangem/wallet/EthEngine.java new file mode 100644 index 0000000000..56be142acc --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/EthEngine.java @@ -0,0 +1,392 @@ +package com.tangem.wallet; + +import android.net.Uri; +import android.util.Log; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; + +import org.bitcoinj.core.ECKey; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Date; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class EthEngine extends CoinEngine{ + + public String GetNextNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + + public int GetNextNodePort(Tangem_Card mCard) + { + return 60001; + } + + public String GetNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + + public int GetNodePort(Tangem_Card mCard) + { + return 60001; + } + + public void SwitchNode(Tangem_Card mCard) + { + } + + public boolean InOutPutVisible() + { + return false; + } + + public String GetBalanceCurrency(Tangem_Card card) + { + return "ETH"; + } + + public boolean AwaitingConfirmation(Tangem_Card card) + { + return false; + } + + public String GetFeeCurrency() + { + return "Gwei"; + } + + public boolean IsNeedCheckNode() + { + return false; + } + + BigDecimal convertToEth(String value) + { + BigInteger m = new BigInteger(value, 10); + BigDecimal n = new BigDecimal(m); + BigDecimal d = n.divide(new BigDecimal("1000000000000000000")); + d = d.setScale(8, RoundingMode.DOWN); + return d; + } + + public int GetTokenDecimals(Tangem_Card card) + { + return 0; + } + + public String GetContractAddress(Tangem_Card card) + { + return ""; + } + + public boolean ValdateAddress(String address, Tangem_Card card) { + + if (address == null || address.isEmpty()) + { + return false; + } + + if(!address.startsWith("0x")&&!address.startsWith("0X")) + { + return false; + } + + if(address.length()!=42) + { + return false; + } + + return true; + } + public String GetBalanceValue(Tangem_Card mCard) + { + String dec = mCard.getDecimalBalance(); + BigDecimal d = convertToEth(dec); + String s = d.toString(); + + String pattern = "#0.000"; // If you like 4 zeros + DecimalFormat myFormatter = new DecimalFormat(pattern); + String output = myFormatter.format(d); + return output; + } + + public static String getAmountEquivalentDescriptionETH(BigDecimal amount, float rateValue) { + if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) + return ""; + + if (rateValue > 0) { + BigDecimal biRate = new BigDecimal(rateValue); + BigDecimal exchangeCurs = biRate.multiply(amount); + exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN); + return "≈ USD  " + exchangeCurs.toString(); + } else { + return "≈ USD  ---"; + } + } + + public static String getAmountEquivalentDescriptionETH(Double amount, float rate) { + if (amount == 0) + return ""; + amount = amount / 100000; + if (rate > 0) { + return String.format("≈ USD %.2f", amount * rate); + } else { + return "≈ USD  ---"; + } + } + + + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + String dec = mCard.getDecimalBalance(); + BigDecimal d = convertToEth(dec); + return getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + @Override + public String GetBalance(Tangem_Card mCard) { + if(!HasBalanceInfo(mCard)){ + return "-- -- -- " + GetBalanceCurrency(mCard); + } + + String output = GetBalanceValue(mCard); + String s = output + " " + GetBalanceCurrency(mCard); + return s; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + return GetBalance(mCard); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + return true; + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + String balance = card.getDecimalBalance(); + if(balance == null || balance == "") + return false; + + BigDecimal bi = new BigDecimal(balance); + + if (BigDecimal.ZERO.compareTo(bi) == 0) + return false; + + return true; + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + BigDecimal d = new BigDecimal(value); + return getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); //new BigDecimal(strAmount); + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + if(amountValue.compareTo(maxValue) > 0 ) + { + return false; + } + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + return card.hasBalanceInfo(); + } + + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("" + mCard.getWallet()); + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + if(mCard.getBlockchain() == Blockchain.EthereumTestNet) + return Uri.parse("https://rinkeby.etherscan.io/address/" + mCard.getWallet()); + else + return Uri.parse("https://etherscan.io/address/" + mCard.getWallet()); + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return true; + } + + + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + Long amount = null; + try { + amount = mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount ==0) + return false; + + + if(fee < minFeeInInternalUnits) + return false; + + + BigDecimal tmpFee = new BigDecimal(feeValue); + BigDecimal tmpAmount = new BigDecimal(amountValue); + tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000")); + + if (tmpFee.compareTo(tmpAmount) > 0) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + BigDecimal gweFee = new BigDecimal(fee); + gweFee = gweFee.divide(new BigDecimal("1000000000")); + gweFee = gweFee.setScale(18, RoundingMode.DOWN); + return GetAmountEqualentDescriptor(mCard, gweFee.toString()); + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + Keccak256 kec = new Keccak256(); + int lenPk = pkUncompressed.length; + if (lenPk < 2) { + throw new IllegalArgumentException("Uncompress public key length is invald"); + } + byte[] cleanKey = new byte[lenPk - 1]; + for (int i = 0; i < cleanKey.length; ++i) { + cleanKey[i] = pkUncompressed[i + 1]; + } + byte[] r = kec.digest(cleanKey); + + byte[] address = new byte[20]; + for (int i = 0; i < 20; ++i) { + address[i] = r[i + 12]; + } + + return String.format("0x%s", BTCUtils.toHex(address)); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + BigInteger nonceValue = mCard.GetConfirmTXCount(); + byte[] pbKey = mCard.getWalletPublicKey(); + boolean flag = (mCard.getSigningMethod()== Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer); + Issuer issuer = mCard.getIssuer(); + + + BigInteger fee = new BigInteger(feeValue, 10); + + BigDecimal amountDec = new BigDecimal(amountValue); + amountDec = amountDec.multiply(new BigDecimal("1000000000")); + + + BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10); + amount = amount.subtract(fee); + + BigInteger nonce = nonceValue; + BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(21000); + Integer chainId = mCard.getBlockchain() == Blockchain.Ethereum ? ETH_Transaction.ChainEnum.Mainnet.getValue() : ETH_Transaction.ChainEnum.Rinkeby.getValue(); + + Long multiplicator = 1000000000L; + amount = amount.multiply(BigInteger.valueOf(multiplicator)); + gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator)); + + String to = toValue; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + ETH_Transaction tx = ETH_Transaction.create(to, amount, nonce, gasPrice, gasLimit, chainId); + + byte[][] hashesForSign = new byte[1][]; + byte[] for_hash = tx.getRawHash(); + hashesForSign[0] = for_hash; + + byte[] signFromCard = null; + try { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } catch (Exception ex) { + Log.e("ETH", ex.getMessage()); + return null; + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if(!f) + { + Log.e("ETH-CHECK", "Sign Failed."); + } + + tx.signature = new ECDSASignature_ETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e("ETH", "invalid v"); + return null; + } + tx.signature.v = (byte) v; + Log.e("ETH_v", String.valueOf(v)); + + byte[] realTX = tx.getEncoded(); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/ExchangeRequest.java b/app/src/main/java/com/tangem/wallet/ExchangeRequest.java new file mode 100644 index 0000000000..942cdbe112 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ExchangeRequest.java @@ -0,0 +1,92 @@ +package com.tangem.wallet; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by dvol on 16.07.2017. + */ + +public class ExchangeRequest { + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public String currency; + public String currencyAlter; + + private ExchangeRequest() { + } + + public ExchangeRequest(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public JSONArray getAnswerList() throws JSONException { + return new JSONArray(answerData); + } + + public String getAsString() { + return jsRequestData.toString(); + } + + public void setID(int value) { + try { + jsRequestData.put("id", String.format("%d", value)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public static ExchangeRequest GetRate(String wallet, String currency, String alterCurrency) { + ExchangeRequest request = new ExchangeRequest(); + request.WalletAddress=wallet; + request.currency = currency; + request.currencyAlter = alterCurrency; + return request; + } + + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } +} diff --git a/app/src/main/java/com/tangem/wallet/ExchangeTask.java b/app/src/main/java/com/tangem/wallet/ExchangeTask.java new file mode 100644 index 0000000000..a47d149d96 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/ExchangeTask.java @@ -0,0 +1,66 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 16.01.2018. + */ + +import android.os.AsyncTask; + +import com.tangem.wallet.ExchangeRequest; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Ilia on 04.12.2017. + */ + +public class ExchangeTask extends AsyncTask> { + public ExchangeTask() + { + + } + protected List doInBackground(ExchangeRequest... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (ExchangeRequest request: result) + { + HttpURLConnection httpcon = null; + + try { + + URL url = new URL("https://api.coinmarketcap.com/v1/ticker/?convert=USD&lmit=10"); + httpcon = (HttpURLConnection) url.openConnection(); + httpcon.setRequestMethod("GET"); + + httpcon.connect(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(httpcon.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + request.answerData = response.toString(); + + } catch (Exception e) { + request.error = e.getMessage(); + } finally { + httpcon.disconnect(); + } + } + + return result; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Fee_Request.java b/app/src/main/java/com/tangem/wallet/Fee_Request.java new file mode 100644 index 0000000000..103ed9fb39 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Fee_Request.java @@ -0,0 +1,102 @@ +package com.tangem.wallet; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Fee_Request { + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public long txSize = 0; + + private Fee_Request() { + } + + public Fee_Request(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public String getAsString() { + return answerData; + } + + public static final int PRIORITY = 2; + public static final int NORMAL = 3; + public static final int MINIMAL = 6; + private int blockCount = NORMAL; + public void setBlockCount(int count + ) + { + blockCount = count; + } + + public int getBlockCount() + { + return blockCount; + } + + public void setID(int value) { + try { + jsRequestData.put("id", String.format("%d", value)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public static Fee_Request GetFee(String wallet, long txSize, int blockCount) { + Fee_Request request = new Fee_Request(); + request.WalletAddress=wallet; + request.txSize = txSize; + request.setBlockCount(blockCount); + return request; + } + + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } +} diff --git a/app/src/main/java/com/tangem/wallet/Fee_Task.java b/app/src/main/java/com/tangem/wallet/Fee_Task.java new file mode 100644 index 0000000000..a8c5c35a94 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Fee_Task.java @@ -0,0 +1,63 @@ +package com.tangem.wallet; + +import android.os.AsyncTask; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Ilia on 04.12.2017. + */ + +public class Fee_Task extends AsyncTask> { + + SharedData sharedCounter = null; + + public Fee_Task(SharedData sharedData) + { + sharedCounter = sharedData; + } + protected List doInBackground(Fee_Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (Fee_Request request: result) + { + HttpURLConnection httpcon = null; + + try { + + URL url = new URL("https://estimatefee.com/n/"+String.valueOf(request.getBlockCount())); + httpcon = (HttpURLConnection) url.openConnection(); + httpcon.setRequestMethod("GET"); + + httpcon.connect(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(httpcon.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + request.answerData = response.toString(); + + } catch (Exception e) { + request.error = e.getMessage(); + } finally { + httpcon.disconnect(); + } + } + + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/FingerprintHelper.java b/app/src/main/java/com/tangem/wallet/FingerprintHelper.java new file mode 100644 index 0000000000..b4fb689afe --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/FingerprintHelper.java @@ -0,0 +1,62 @@ +package com.tangem.wallet; + +import android.annotation.TargetApi; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.CancellationSignal; + +/** + * Created by dtaka on 8/20/2016. + */ +@TargetApi(Build.VERSION_CODES.M) +public class FingerprintHelper extends FingerprintManager.AuthenticationCallback { + private FingerprintHelperListener listener; + + public FingerprintHelper(FingerprintHelperListener listener) { + this.listener = listener; + } + + private CancellationSignal cancellationSignal; + + public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { + cancellationSignal = new CancellationSignal(); + + try { + manager.authenticate(cryptoObject, cancellationSignal, 0, this, null); + } catch (SecurityException ex) { + listener.authenticationFailed("An error occurred:\n" + ex.getMessage()); + } catch (Exception ex) { + listener.authenticationFailed("An error occurred\n" + ex.getMessage()); + } + } + + public void cancel() { + if (cancellationSignal != null) + cancellationSignal.cancel(); + } + + interface FingerprintHelperListener { + public void authenticationFailed(String error); + public void authenticationSucceeded(FingerprintManager.AuthenticationResult result); + } + + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + listener.authenticationFailed("Authentication error\n" + errString); + } + + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + listener.authenticationFailed("Authentication help\n" + helpString); + } + + @Override + public void onAuthenticationFailed() { + listener.authenticationFailed("Authentication failed."); + } + + @Override + public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { + listener.authenticationSucceeded(result); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/FormatUtil.java b/app/src/main/java/com/tangem/wallet/FormatUtil.java new file mode 100644 index 0000000000..4a21adb789 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/FormatUtil.java @@ -0,0 +1,51 @@ +package com.tangem.wallet; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * Created by Ilia on 15.02.2018. + */ + +public class FormatUtil { + public static long parseValue(String valueStr) throws NumberFormatException { + return new BigDecimal(valueStr).multiply(BigDecimal.valueOf(1_0000_0000)).setScale(0, BigDecimal.ROUND_HALF_DOWN).longValueExact(); + } + + public static String DoubleToString(double amount) + { + DecimalFormat myFormatter = GetDecimalFormat(); + String output = myFormatter.format(amount); + return output; + } + + public static DecimalFormat GetDecimalFormat() + { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator('.'); + + String pattern = "#0.######"; + DecimalFormat myFormatter = new DecimalFormat(pattern, symbols); + + myFormatter.setParseBigDecimal(true); + + return myFormatter; + } + + + public static long ConvertStringToLong(String caption) throws Exception { + + + BigDecimal d = new BigDecimal(caption); + d = d.multiply(new BigDecimal(100000)); + d = d.setScale(5); + BigInteger b = d.toBigInteger(); + long l = b.longValue(); + return l; + + } +} diff --git a/app/src/main/java/com/tangem/wallet/Infura_Request.java b/app/src/main/java/com/tangem/wallet/Infura_Request.java new file mode 100644 index 0000000000..1f3465b6e9 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Infura_Request.java @@ -0,0 +1,197 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 19.12.2017. + */ + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + +public class Infura_Request { + public static final String METHOD_ETH_GetBalance = "eth_getBalance"; + public static final String METHOD_ETH_GetOutTransactionCount = "eth_getTransactionCount"; + public static final String METHOD_ETH_GetGasPrice = "eth_gasPrice"; + public static final String METHOD_ETH_SendRawTransaction = "eth_sendRawTransaction"; + public static final String METHOD_ETH_Call = "eth_call"; + + public JSONObject jsRequestData; + public String answerData; + public String error; + public String WalletAddress; + public int Dec; + public String amount; + public Blockchain blockchain; + + public void setBlockchain(Blockchain value) { + blockchain = value; + } + + public Blockchain getBlockchain() + { + return blockchain; + } + + private Infura_Request() { + } + + public Infura_Request(JSONObject jsRequest) { + try { + jsRequestData = new JSONObject(jsRequest.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getAnswer() { + try { + return new JSONObject(answerData); + } catch (Exception e) { + try { + return new JSONObject(String.format("[\"Error\":\"%s\"]", e.getMessage())); + } catch (JSONException e1) { + e1.printStackTrace(); + return null; + } + } + } + + public String getAsString() { + return jsRequestData.toString(); + } + + public void setID(int value) { + try { + jsRequestData.put("id", value/*String.format("%d", value)*/); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getID() { + try { + return jsRequestData.getInt("id"); + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + + public boolean isMethod(String methodName) throws JSONException { + return jsRequestData.getString("method").equals(methodName); + } + + public static Infura_Request GetBalance(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetBalance + "\", \"params\":[\"" + wallet + "\", \"latest\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request GetTokenBalance(String wallet, String contract, int dec) + { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.Dec = dec; + String address = wallet.substring(2); + String dataValue = String.format("{\"data\": \"0x70a08231000000000000000000000000%s\", \"to\": \"%s\"}", address, contract); + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_Call + "\", \"params\":[" +dataValue+", \"latest\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request SendTransaction(String wallet, String tx) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_SendRawTransaction + "\", \"params\":[\"" + tx + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + + public static Infura_Request GetOutTransactionCount(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetOutTransactionCount + "\", \"params\":[\"" + wallet + "\", \"latest\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request GetPendingTransactionCount(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetOutTransactionCount + "\", \"params\":[\"" + wallet + "\", \"pending\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + public static Infura_Request GetGasPrise(String wallet) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_GetGasPrice + "\", \"params\":[] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + public static Infura_Request SendTransactionCount(String wallet, String TX) { + Infura_Request request = new Infura_Request(); + try { + request.WalletAddress=wallet; + request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_ETH_SendRawTransaction + "\", \"params\":[\"" + TX + "\"] }"); + } catch (JSONException e) { + e.printStackTrace(); + request.error = e.toString(); + } + return request; + } + + + + //METHOD_ETH_GetOutTransactionCount + + + public JSONArray getParams() throws JSONException { + return jsRequestData.getJSONArray("params"); + } + + public JSONObject getResult() throws JSONException { + return getAnswer().getJSONObject("result"); + } + + public String getResultString() throws JSONException { + return getAnswer().getString("result"); + } + + public JSONArray getResultArray() throws JSONException { + return getAnswer().getJSONArray("result"); + } +} + diff --git a/app/src/main/java/com/tangem/wallet/Infura_Task.java b/app/src/main/java/com/tangem/wallet/Infura_Task.java new file mode 100644 index 0000000000..7e0ded0162 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Infura_Task.java @@ -0,0 +1,134 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 19.12.2017. + */ + +import android.os.AsyncTask; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; + +/** + * Created by Ilia on 04.12.2017. + */ + +public class Infura_Task extends AsyncTask> { + private Exception exception; + private Blockchain blockchain; + public Infura_Task(Blockchain blockchainNet) + { + blockchain = blockchainNet; + } + boolean useOurNode = false; + protected List doInBackground(Infura_Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (Infura_Request request: result) + { + HttpURLConnection httpcon = null; + + try { + URL url = new URL("https://rinkeby.infura.io/AfWg0tmYEX5Kukn2UkKV"); + + if(blockchain == Blockchain.Ethereum || blockchain == Blockchain.Token){ + if(useOurNode) { + URL tmp = new URL("http://52.230.23.88"); + url = new URL(tmp.getProtocol(), tmp.getHost(), 27172, tmp.getFile()); + }else + url = new URL("https://mainnet.infura.io/AfWg0tmYEX5Kukn2UkKV"); + + } + + if(useOurNode) + { + httpcon = (HttpURLConnection)url.openConnection(); + } + else + { + httpcon = (HttpsURLConnection)url.openConnection(); + } + + if(httpcon == null) + { + request.error = String.format("Cann't connect to %s", url.getHost()); + return result; + } + + httpcon.setRequestMethod("POST"); + httpcon.setRequestProperty("Content-Type", "application/json"); + String params = request.getAsString(); + + OutputStream os = httpcon.getOutputStream(); + if(os == null) + { + request.error = String.format("Cann't recieve data from %s", url.getHost()); + return result; + } + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(os, "UTF-8")); + if(writer == null) + { + request.error = String.format("Cann't send data to %s", url.getHost()); + + } + writer.write(params); + writer.flush(); + writer.close(); + os.close(); + + + httpcon.connect(); + request.getParams(); + System.out.println("code:"+httpcon.getResponseCode()); + int code = httpcon.getResponseCode(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(httpcon.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + request.answerData = response.toString(); + + } catch (Exception e) { + this.exception = e; + request.error = e.getMessage(); + } finally { + httpcon.disconnect(); + } + } + + return result; + } + + public String getValidationNodeDescription() { + if(blockchain == Blockchain.Ethereum || blockchain == Blockchain.Token) + { + if(useOurNode) + return "52.230.23.88:27172"; + else + return "Infura, infura.io"; + } + + + return "Infura, rinkeby.infura.io"; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/Issuer.java b/app/src/main/java/com/tangem/wallet/Issuer.java new file mode 100644 index 0000000000..2c1a50ee73 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Issuer.java @@ -0,0 +1,126 @@ +package com.tangem.wallet; + +import com.tangem.cardReader.CardCrypto; + +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; + +import java.math.BigInteger; +import java.util.Arrays; + +import static com.tangem.cardReader.CardCrypto.*; + +/** + * Created by dvol on 14.11.2017. + */ + +public enum Issuer { + Unknown("Unknown", "Unknown", null, null, null, null), + SMART_CASH_AG("SMART CASH AG", "SMART CASH AG", + IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), + IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), + TANGEM_SDK("TANGEM SDK", "TANGEM SDK", + IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), + IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), + TANGEM("TANGEM", "TANGEM", null, IssuerKeyStorage.tangemPublicDataKey, null, IssuerKeyStorage.tangemPublicTransactionKey) + ; + + + static class IssuerKeyStorage { + private static final byte[] sdkPrivateDataKey = new byte[]{ + (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, + (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, + (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, + (byte) 0x39, (byte) 0x27, (byte) 0x37, (byte) 0x2D, (byte) 0x40, (byte) 0xDA, (byte) 0x9E, (byte) 0x92}; + + private static final byte[] sdkPrivateTransactionKey = new byte[]{ + (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, + (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, + (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, + (byte) 0x19, (byte) 0x18, (byte) 0x17, (byte) 0x16, (byte) 0x15, (byte) 0x14, (byte) 0x13, (byte) 0x12}; + + private static byte[] tangemPublicDataKey = { + (byte) 0x04 , + (byte) 0x81 ,(byte) 0x96 ,(byte) 0xAA ,(byte) 0x4B ,(byte) 0x41 ,(byte) 0x0A ,(byte) 0xC4 ,(byte) 0x4A, + (byte) 0x3B ,(byte) 0x9C ,(byte) 0xCE ,(byte) 0x18 ,(byte) 0xE7 ,(byte) 0xBE ,(byte) 0x22 ,(byte) 0x6A, + (byte) 0xEA ,(byte) 0x07 ,(byte) 0x0A ,(byte) 0xCC ,(byte) 0x83 ,(byte) 0xA9 ,(byte) 0xCF ,(byte) 0x67, + (byte) 0x54 ,(byte) 0x0F ,(byte) 0xAC ,(byte) 0x49 ,(byte) 0xAF ,(byte) 0x25 ,(byte) 0x12 ,(byte) 0x9F, + (byte) 0x6A ,(byte) 0x53 ,(byte) 0x8A ,(byte) 0x28 ,(byte) 0xAD ,(byte) 0x63 ,(byte) 0x41 ,(byte) 0x35, + (byte) 0x8E ,(byte) 0x3C ,(byte) 0x4F ,(byte) 0x99 ,(byte) 0x63 ,(byte) 0x06 ,(byte) 0x4F ,(byte) 0x7E, + (byte) 0x36 ,(byte) 0x53 ,(byte) 0x72 ,(byte) 0xA6 ,(byte) 0x51 ,(byte) 0xD3 ,(byte) 0x74 ,(byte) 0xE5, + (byte) 0xC2 ,(byte) 0x3C ,(byte) 0xDD ,(byte) 0x37 ,(byte) 0xFD ,(byte) 0x09 ,(byte) 0x9B ,(byte) 0xF2}; + + private static byte[] tangemPublicTransactionKey = { + (byte) 0x04 , + (byte) 0x34 ,(byte) 0x3D ,(byte) 0x40 ,(byte) 0x49 ,(byte) 0x6C ,(byte) 0xBE ,(byte) 0x1F ,(byte) 0xE8, + (byte) 0xA8 ,(byte) 0xC0 ,(byte) 0x26 ,(byte) 0x57 ,(byte) 0x5C ,(byte) 0x43 ,(byte) 0x5A ,(byte) 0x29, + (byte) 0x14 ,(byte) 0x1E ,(byte) 0xA3 ,(byte) 0xBC ,(byte) 0x33 ,(byte) 0x5D ,(byte) 0xA5 ,(byte) 0x54, + (byte) 0x9A ,(byte) 0xB6 ,(byte) 0xC6 ,(byte) 0x46 ,(byte) 0x85 ,(byte) 0xA6 ,(byte) 0x46 ,(byte) 0x84, + (byte) 0x80 ,(byte) 0x36 ,(byte) 0xD4 ,(byte) 0x81 ,(byte) 0xCF ,(byte) 0x9A ,(byte) 0x98 ,(byte) 0x93, + (byte) 0x90 ,(byte) 0xA8 ,(byte) 0xB0 ,(byte) 0x34 ,(byte) 0xB2 ,(byte) 0x29 ,(byte) 0xD9 ,(byte) 0x9B, + (byte) 0xD4 ,(byte) 0x9E ,(byte) 0x6F ,(byte) 0x07 ,(byte) 0xD2 ,(byte) 0xFF ,(byte) 0x02 ,(byte) 0x74, + (byte) 0x6E ,(byte) 0xA2 ,(byte) 0x65 ,(byte) 0xEF ,(byte) 0x99 ,(byte) 0x38 ,(byte) 0x0A ,(byte) 0x80}; + + public static byte[] GeneratePublicKey(byte[] privateKey) { + try { + return CardCrypto.GeneratePublicKey(privateKey); + } + catch (Exception e) + { + e.printStackTrace(); + return null; + } + } + } + + private String ID; + private String officialName; + private byte[] privateDataKeyArray; + private byte[] publicDataKeyArray; + private byte[] privateTransactionKeyArray; + private byte[] publicTransactionKeyArray; + + Issuer(String id, String officialName, byte[] privateDataKey, byte[] publicDataKey, byte[] privateTransactionKey, byte[] publicTransactionKey) { + this.ID = id; + this.officialName = officialName; + this.privateDataKeyArray = privateDataKey; + this.privateTransactionKeyArray = privateTransactionKey; + this.publicDataKeyArray = publicDataKey; + this.publicTransactionKeyArray = publicTransactionKey; + } + + public byte[] getPublicDataKey() { + return publicDataKeyArray; + } + + public byte[] getPublicTransactionKey() { + return publicTransactionKeyArray; + } + + public byte[] getPrivateDataKey() { + return privateDataKeyArray; + } + + public byte[] getPrivateTransactionKey() { + return privateTransactionKeyArray; + } + + public byte[] getID() { + return ID.getBytes(); + } + + public String getOfficialName() { + return officialName; + } + + public static Issuer FindIssuer(String ID, byte[] publicDataKey) { + Issuer[] issuers = Issuer.values(); + for (int i = 1; i < issuers.length; i++) { + if (issuers[i].ID.equals(ID) && Arrays.equals(issuers[i].getPublicDataKey(), publicDataKey)) { + return issuers[i]; + } + } + return Issuer.Unknown; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/Keccak256.java b/app/src/main/java/com/tangem/wallet/Keccak256.java new file mode 100644 index 0000000000..bd8c2b5ded --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Keccak256.java @@ -0,0 +1,39 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 18.12.2017. + */ + +public class Keccak256 extends KeccakCore { + + /** + * Create the engine. + */ + public Keccak256() + { + super("eth-keccak-256"); + } + + public Digest copy() + { + return copyState(new Keccak256()); + } + + public int engineGetDigestLength() + { + return 32; + } + + @Override + protected byte[] engineDigest() { + return null; + } + + @Override + protected void engineUpdate(byte arg0) { + } + + @Override + protected void engineUpdate(byte[] arg0, int arg1, int arg2) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/KeccakCore.java b/app/src/main/java/com/tangem/wallet/KeccakCore.java new file mode 100644 index 0000000000..4eafa6de60 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/KeccakCore.java @@ -0,0 +1,546 @@ +package com.tangem.wallet; + + +/** + * Created by Ilia on 18.12.2017. + */ +abstract class KeccakCore extends DigestEngine{ + KeccakCore(String alg) + { + super(alg); + } + + private long[] A; + private byte[] tmpOut; + + private static final long[] RC = { + 0x0000000000000001L, 0x0000000000008082L, + 0x800000000000808AL, 0x8000000080008000L, + 0x000000000000808BL, 0x0000000080000001L, + 0x8000000080008081L, 0x8000000000008009L, + 0x000000000000008AL, 0x0000000000000088L, + 0x0000000080008009L, 0x000000008000000AL, + 0x000000008000808BL, 0x800000000000008BL, + 0x8000000000008089L, 0x8000000000008003L, + 0x8000000000008002L, 0x8000000000000080L, + 0x000000000000800AL, 0x800000008000000AL, + 0x8000000080008081L, 0x8000000000008080L, + 0x0000000080000001L, 0x8000000080008008L + }; + + /** + * Encode the 64-bit word {@code val} into the array + * {@code buf} at offset {@code off}, in little-endian + * convention (least significant byte first). + * + * @param val the value to encode + * @param buf the destination buffer + * @param off the destination offset + */ + private static void encodeLELong(long val, byte[] buf, int off) + { + buf[off + 0] = (byte)val; + buf[off + 1] = (byte)(val >>> 8); + buf[off + 2] = (byte)(val >>> 16); + buf[off + 3] = (byte)(val >>> 24); + buf[off + 4] = (byte)(val >>> 32); + buf[off + 5] = (byte)(val >>> 40); + buf[off + 6] = (byte)(val >>> 48); + buf[off + 7] = (byte)(val >>> 56); + } + + /** + * Decode a 64-bit little-endian word from the array {@code buf} + * at offset {@code off}. + * + * @param buf the source buffer + * @param off the source offset + * @return the decoded value + */ + private static long decodeLELong(byte[] buf, int off) + { + return (buf[off + 0] & 0xFFL) + | ((buf[off + 1] & 0xFFL) << 8) + | ((buf[off + 2] & 0xFFL) << 16) + | ((buf[off + 3] & 0xFFL) << 24) + | ((buf[off + 4] & 0xFFL) << 32) + | ((buf[off + 5] & 0xFFL) << 40) + | ((buf[off + 6] & 0xFFL) << 48) + | ((buf[off + 7] & 0xFFL) << 56); + } + + protected void engineReset() + { + doReset(); + } + + protected void processBlock(byte[] data) + { + /* Input block */ + for (int i = 0; i < data.length; i += 8) + A[i >>> 3] ^= decodeLELong(data, i); + + long t0, t1, t2, t3, t4; + long tt0, tt1, tt2, tt3, tt4; + long t, kt; + long c0, c1, c2, c3, c4, bnn; + + /* + * Unrolling four rounds kills performance big time + * on Intel x86 Core2, in both 32-bit and 64-bit modes + * (less than 1 MB/s instead of 55 MB/s on x86-64). + * Unrolling two rounds appears to be fine. + */ + for (int j = 0; j < 24; j += 2) { + + tt0 = A[ 1] ^ A[ 6]; + tt1 = A[11] ^ A[16]; + tt0 ^= A[21] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 4] ^ A[ 9]; + tt3 = A[14] ^ A[19]; + tt0 ^= A[24]; + tt2 ^= tt3; + t0 = tt0 ^ tt2; + + tt0 = A[ 2] ^ A[ 7]; + tt1 = A[12] ^ A[17]; + tt0 ^= A[22] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 0] ^ A[ 5]; + tt3 = A[10] ^ A[15]; + tt0 ^= A[20]; + tt2 ^= tt3; + t1 = tt0 ^ tt2; + + tt0 = A[ 3] ^ A[ 8]; + tt1 = A[13] ^ A[18]; + tt0 ^= A[23] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 1] ^ A[ 6]; + tt3 = A[11] ^ A[16]; + tt0 ^= A[21]; + tt2 ^= tt3; + t2 = tt0 ^ tt2; + + tt0 = A[ 4] ^ A[ 9]; + tt1 = A[14] ^ A[19]; + tt0 ^= A[24] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 2] ^ A[ 7]; + tt3 = A[12] ^ A[17]; + tt0 ^= A[22]; + tt2 ^= tt3; + t3 = tt0 ^ tt2; + + tt0 = A[ 0] ^ A[ 5]; + tt1 = A[10] ^ A[15]; + tt0 ^= A[20] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 3] ^ A[ 8]; + tt3 = A[13] ^ A[18]; + tt0 ^= A[23]; + tt2 ^= tt3; + t4 = tt0 ^ tt2; + + A[ 0] = A[ 0] ^ t0; + A[ 5] = A[ 5] ^ t0; + A[10] = A[10] ^ t0; + A[15] = A[15] ^ t0; + A[20] = A[20] ^ t0; + A[ 1] = A[ 1] ^ t1; + A[ 6] = A[ 6] ^ t1; + A[11] = A[11] ^ t1; + A[16] = A[16] ^ t1; + A[21] = A[21] ^ t1; + A[ 2] = A[ 2] ^ t2; + A[ 7] = A[ 7] ^ t2; + A[12] = A[12] ^ t2; + A[17] = A[17] ^ t2; + A[22] = A[22] ^ t2; + A[ 3] = A[ 3] ^ t3; + A[ 8] = A[ 8] ^ t3; + A[13] = A[13] ^ t3; + A[18] = A[18] ^ t3; + A[23] = A[23] ^ t3; + A[ 4] = A[ 4] ^ t4; + A[ 9] = A[ 9] ^ t4; + A[14] = A[14] ^ t4; + A[19] = A[19] ^ t4; + A[24] = A[24] ^ t4; + A[ 5] = (A[ 5] << 36) | (A[ 5] >>> (64 - 36)); + A[10] = (A[10] << 3) | (A[10] >>> (64 - 3)); + A[15] = (A[15] << 41) | (A[15] >>> (64 - 41)); + A[20] = (A[20] << 18) | (A[20] >>> (64 - 18)); + A[ 1] = (A[ 1] << 1) | (A[ 1] >>> (64 - 1)); + A[ 6] = (A[ 6] << 44) | (A[ 6] >>> (64 - 44)); + A[11] = (A[11] << 10) | (A[11] >>> (64 - 10)); + A[16] = (A[16] << 45) | (A[16] >>> (64 - 45)); + A[21] = (A[21] << 2) | (A[21] >>> (64 - 2)); + A[ 2] = (A[ 2] << 62) | (A[ 2] >>> (64 - 62)); + A[ 7] = (A[ 7] << 6) | (A[ 7] >>> (64 - 6)); + A[12] = (A[12] << 43) | (A[12] >>> (64 - 43)); + A[17] = (A[17] << 15) | (A[17] >>> (64 - 15)); + A[22] = (A[22] << 61) | (A[22] >>> (64 - 61)); + A[ 3] = (A[ 3] << 28) | (A[ 3] >>> (64 - 28)); + A[ 8] = (A[ 8] << 55) | (A[ 8] >>> (64 - 55)); + A[13] = (A[13] << 25) | (A[13] >>> (64 - 25)); + A[18] = (A[18] << 21) | (A[18] >>> (64 - 21)); + A[23] = (A[23] << 56) | (A[23] >>> (64 - 56)); + A[ 4] = (A[ 4] << 27) | (A[ 4] >>> (64 - 27)); + A[ 9] = (A[ 9] << 20) | (A[ 9] >>> (64 - 20)); + A[14] = (A[14] << 39) | (A[14] >>> (64 - 39)); + A[19] = (A[19] << 8) | (A[19] >>> (64 - 8)); + A[24] = (A[24] << 14) | (A[24] >>> (64 - 14)); + bnn = ~A[12]; + kt = A[ 6] | A[12]; + c0 = A[ 0] ^ kt; + kt = bnn | A[18]; + c1 = A[ 6] ^ kt; + kt = A[18] & A[24]; + c2 = A[12] ^ kt; + kt = A[24] | A[ 0]; + c3 = A[18] ^ kt; + kt = A[ 0] & A[ 6]; + c4 = A[24] ^ kt; + A[ 0] = c0; + A[ 6] = c1; + A[12] = c2; + A[18] = c3; + A[24] = c4; + bnn = ~A[22]; + kt = A[ 9] | A[10]; + c0 = A[ 3] ^ kt; + kt = A[10] & A[16]; + c1 = A[ 9] ^ kt; + kt = A[16] | bnn; + c2 = A[10] ^ kt; + kt = A[22] | A[ 3]; + c3 = A[16] ^ kt; + kt = A[ 3] & A[ 9]; + c4 = A[22] ^ kt; + A[ 3] = c0; + A[ 9] = c1; + A[10] = c2; + A[16] = c3; + A[22] = c4; + bnn = ~A[19]; + kt = A[ 7] | A[13]; + c0 = A[ 1] ^ kt; + kt = A[13] & A[19]; + c1 = A[ 7] ^ kt; + kt = bnn & A[20]; + c2 = A[13] ^ kt; + kt = A[20] | A[ 1]; + c3 = bnn ^ kt; + kt = A[ 1] & A[ 7]; + c4 = A[20] ^ kt; + A[ 1] = c0; + A[ 7] = c1; + A[13] = c2; + A[19] = c3; + A[20] = c4; + bnn = ~A[17]; + kt = A[ 5] & A[11]; + c0 = A[ 4] ^ kt; + kt = A[11] | A[17]; + c1 = A[ 5] ^ kt; + kt = bnn | A[23]; + c2 = A[11] ^ kt; + kt = A[23] & A[ 4]; + c3 = bnn ^ kt; + kt = A[ 4] | A[ 5]; + c4 = A[23] ^ kt; + A[ 4] = c0; + A[ 5] = c1; + A[11] = c2; + A[17] = c3; + A[23] = c4; + bnn = ~A[ 8]; + kt = bnn & A[14]; + c0 = A[ 2] ^ kt; + kt = A[14] | A[15]; + c1 = bnn ^ kt; + kt = A[15] & A[21]; + c2 = A[14] ^ kt; + kt = A[21] | A[ 2]; + c3 = A[15] ^ kt; + kt = A[ 2] & A[ 8]; + c4 = A[21] ^ kt; + A[ 2] = c0; + A[ 8] = c1; + A[14] = c2; + A[15] = c3; + A[21] = c4; + A[ 0] = A[ 0] ^ RC[j + 0]; + + tt0 = A[ 6] ^ A[ 9]; + tt1 = A[ 7] ^ A[ 5]; + tt0 ^= A[ 8] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[24] ^ A[22]; + tt3 = A[20] ^ A[23]; + tt0 ^= A[21]; + tt2 ^= tt3; + t0 = tt0 ^ tt2; + + tt0 = A[12] ^ A[10]; + tt1 = A[13] ^ A[11]; + tt0 ^= A[14] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 0] ^ A[ 3]; + tt3 = A[ 1] ^ A[ 4]; + tt0 ^= A[ 2]; + tt2 ^= tt3; + t1 = tt0 ^ tt2; + + tt0 = A[18] ^ A[16]; + tt1 = A[19] ^ A[17]; + tt0 ^= A[15] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[ 6] ^ A[ 9]; + tt3 = A[ 7] ^ A[ 5]; + tt0 ^= A[ 8]; + tt2 ^= tt3; + t2 = tt0 ^ tt2; + + tt0 = A[24] ^ A[22]; + tt1 = A[20] ^ A[23]; + tt0 ^= A[21] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[12] ^ A[10]; + tt3 = A[13] ^ A[11]; + tt0 ^= A[14]; + tt2 ^= tt3; + t3 = tt0 ^ tt2; + + tt0 = A[ 0] ^ A[ 3]; + tt1 = A[ 1] ^ A[ 4]; + tt0 ^= A[ 2] ^ tt1; + tt0 = (tt0 << 1) | (tt0 >>> 63); + tt2 = A[18] ^ A[16]; + tt3 = A[19] ^ A[17]; + tt0 ^= A[15]; + tt2 ^= tt3; + t4 = tt0 ^ tt2; + + A[ 0] = A[ 0] ^ t0; + A[ 3] = A[ 3] ^ t0; + A[ 1] = A[ 1] ^ t0; + A[ 4] = A[ 4] ^ t0; + A[ 2] = A[ 2] ^ t0; + A[ 6] = A[ 6] ^ t1; + A[ 9] = A[ 9] ^ t1; + A[ 7] = A[ 7] ^ t1; + A[ 5] = A[ 5] ^ t1; + A[ 8] = A[ 8] ^ t1; + A[12] = A[12] ^ t2; + A[10] = A[10] ^ t2; + A[13] = A[13] ^ t2; + A[11] = A[11] ^ t2; + A[14] = A[14] ^ t2; + A[18] = A[18] ^ t3; + A[16] = A[16] ^ t3; + A[19] = A[19] ^ t3; + A[17] = A[17] ^ t3; + A[15] = A[15] ^ t3; + A[24] = A[24] ^ t4; + A[22] = A[22] ^ t4; + A[20] = A[20] ^ t4; + A[23] = A[23] ^ t4; + A[21] = A[21] ^ t4; + A[ 3] = (A[ 3] << 36) | (A[ 3] >>> (64 - 36)); + A[ 1] = (A[ 1] << 3) | (A[ 1] >>> (64 - 3)); + A[ 4] = (A[ 4] << 41) | (A[ 4] >>> (64 - 41)); + A[ 2] = (A[ 2] << 18) | (A[ 2] >>> (64 - 18)); + A[ 6] = (A[ 6] << 1) | (A[ 6] >>> (64 - 1)); + A[ 9] = (A[ 9] << 44) | (A[ 9] >>> (64 - 44)); + A[ 7] = (A[ 7] << 10) | (A[ 7] >>> (64 - 10)); + A[ 5] = (A[ 5] << 45) | (A[ 5] >>> (64 - 45)); + A[ 8] = (A[ 8] << 2) | (A[ 8] >>> (64 - 2)); + A[12] = (A[12] << 62) | (A[12] >>> (64 - 62)); + A[10] = (A[10] << 6) | (A[10] >>> (64 - 6)); + A[13] = (A[13] << 43) | (A[13] >>> (64 - 43)); + A[11] = (A[11] << 15) | (A[11] >>> (64 - 15)); + A[14] = (A[14] << 61) | (A[14] >>> (64 - 61)); + A[18] = (A[18] << 28) | (A[18] >>> (64 - 28)); + A[16] = (A[16] << 55) | (A[16] >>> (64 - 55)); + A[19] = (A[19] << 25) | (A[19] >>> (64 - 25)); + A[17] = (A[17] << 21) | (A[17] >>> (64 - 21)); + A[15] = (A[15] << 56) | (A[15] >>> (64 - 56)); + A[24] = (A[24] << 27) | (A[24] >>> (64 - 27)); + A[22] = (A[22] << 20) | (A[22] >>> (64 - 20)); + A[20] = (A[20] << 39) | (A[20] >>> (64 - 39)); + A[23] = (A[23] << 8) | (A[23] >>> (64 - 8)); + A[21] = (A[21] << 14) | (A[21] >>> (64 - 14)); + bnn = ~A[13]; + kt = A[ 9] | A[13]; + c0 = A[ 0] ^ kt; + kt = bnn | A[17]; + c1 = A[ 9] ^ kt; + kt = A[17] & A[21]; + c2 = A[13] ^ kt; + kt = A[21] | A[ 0]; + c3 = A[17] ^ kt; + kt = A[ 0] & A[ 9]; + c4 = A[21] ^ kt; + A[ 0] = c0; + A[ 9] = c1; + A[13] = c2; + A[17] = c3; + A[21] = c4; + bnn = ~A[14]; + kt = A[22] | A[ 1]; + c0 = A[18] ^ kt; + kt = A[ 1] & A[ 5]; + c1 = A[22] ^ kt; + kt = A[ 5] | bnn; + c2 = A[ 1] ^ kt; + kt = A[14] | A[18]; + c3 = A[ 5] ^ kt; + kt = A[18] & A[22]; + c4 = A[14] ^ kt; + A[18] = c0; + A[22] = c1; + A[ 1] = c2; + A[ 5] = c3; + A[14] = c4; + bnn = ~A[23]; + kt = A[10] | A[19]; + c0 = A[ 6] ^ kt; + kt = A[19] & A[23]; + c1 = A[10] ^ kt; + kt = bnn & A[ 2]; + c2 = A[19] ^ kt; + kt = A[ 2] | A[ 6]; + c3 = bnn ^ kt; + kt = A[ 6] & A[10]; + c4 = A[ 2] ^ kt; + A[ 6] = c0; + A[10] = c1; + A[19] = c2; + A[23] = c3; + A[ 2] = c4; + bnn = ~A[11]; + kt = A[ 3] & A[ 7]; + c0 = A[24] ^ kt; + kt = A[ 7] | A[11]; + c1 = A[ 3] ^ kt; + kt = bnn | A[15]; + c2 = A[ 7] ^ kt; + kt = A[15] & A[24]; + c3 = bnn ^ kt; + kt = A[24] | A[ 3]; + c4 = A[15] ^ kt; + A[24] = c0; + A[ 3] = c1; + A[ 7] = c2; + A[11] = c3; + A[15] = c4; + bnn = ~A[16]; + kt = bnn & A[20]; + c0 = A[12] ^ kt; + kt = A[20] | A[ 4]; + c1 = bnn ^ kt; + kt = A[ 4] & A[ 8]; + c2 = A[20] ^ kt; + kt = A[ 8] | A[12]; + c3 = A[ 4] ^ kt; + kt = A[12] & A[16]; + c4 = A[ 8] ^ kt; + A[12] = c0; + A[16] = c1; + A[20] = c2; + A[ 4] = c3; + A[ 8] = c4; + A[ 0] = A[ 0] ^ RC[j + 1]; + t = A[ 5]; + A[ 5] = A[18]; + A[18] = A[11]; + A[11] = A[10]; + A[10] = A[ 6]; + A[ 6] = A[22]; + A[22] = A[20]; + A[20] = A[12]; + A[12] = A[19]; + A[19] = A[15]; + A[15] = A[24]; + A[24] = A[ 8]; + A[ 8] = t; + t = A[ 1]; + A[ 1] = A[ 9]; + A[ 9] = A[14]; + A[14] = A[ 2]; + A[ 2] = A[13]; + A[13] = A[23]; + A[23] = A[ 4]; + A[ 4] = A[21]; + A[21] = A[16]; + A[16] = A[ 3]; + A[ 3] = A[17]; + A[17] = A[ 7]; + A[ 7] = t; + } + } + + protected void doPadding(byte[] out, int off) + { + int ptr = flush(); + byte[] buf = getBlockBuffer(); + if ((ptr + 1) == buf.length) { + buf[ptr] = (byte)0x81; + } else { + buf[ptr] = (byte)0x01; + for (int i = ptr + 1; i < (buf.length - 1); i ++) + buf[i] = 0; + buf[buf.length - 1] = (byte)0x80; + } + processBlock(buf); + A[ 1] = ~A[ 1]; + A[ 2] = ~A[ 2]; + A[ 8] = ~A[ 8]; + A[12] = ~A[12]; + A[17] = ~A[17]; + A[20] = ~A[20]; + int dlen = engineGetDigestLength(); + for (int i = 0; i < dlen; i += 8) + encodeLELong(A[i >>> 3], tmpOut, i); + System.arraycopy(tmpOut, 0, out, off, dlen); + } + + protected void doInit() + { + A = new long[25]; + tmpOut = new byte[(engineGetDigestLength() + 7) & ~7]; + doReset(); + } + + public int getBlockLength() + { + return 200 - 2 * engineGetDigestLength(); + } + + private final void doReset() + { + for (int i = 0; i < 25; i ++) + A[i] = 0; + A[ 1] = 0xFFFFFFFFFFFFFFFFL; + A[ 2] = 0xFFFFFFFFFFFFFFFFL; + A[ 8] = 0xFFFFFFFFFFFFFFFFL; + A[12] = 0xFFFFFFFFFFFFFFFFL; + A[17] = 0xFFFFFFFFFFFFFFFFL; + A[20] = 0xFFFFFFFFFFFFFFFFL; + } + + + protected Digest copyState(KeccakCore dst) + { + System.arraycopy(A, 0, dst.A, 0, 25); + return super.copyState(dst); + } + + public String toString() + { + return "Keccak-" + (engineGetDigestLength() << 3); + } +} diff --git a/app/src/main/java/com/tangem/wallet/LastSignStorage.java b/app/src/main/java/com/tangem/wallet/LastSignStorage.java new file mode 100644 index 0000000000..73dc61b561 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LastSignStorage.java @@ -0,0 +1,146 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.lang.reflect.Array; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +/** + * Created by dvol on 30.10.2017. + */ + +public class LastSignStorage { + + private static SharedPreferences sharedPreferences=null; + + private static Set cards = new ArraySet<>(); + private static Map dates = new ArrayMap<>(); + private static Map txCol = new ArrayMap<>(); + private static Map txCompleteCol = new ArrayMap<>(); + + static void Init(Context context) { + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + cards=sharedPreferences.getStringSet("LastSign_Cards", cards); + for (int i = 0; i < cards.size(); i++) { + String wallet = cards.toArray()[i].toString(); + Date dt = new Date(); + dt.setTime(sharedPreferences.getLong("LastSign_" + wallet, 0)); + dates.put(wallet, dt); + } + } + + public static boolean needInit() { + return sharedPreferences==null; + } + + static class CompleteTx + { + public String TX; + public boolean isComplete; + } + public static Map GetTxList() + { + Set wallets=sharedPreferences.getStringSet("LastSign_Cards", cards); + Map 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 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 txList = GetTxList(); + if(txList.containsKey(wallet)) + { + boolean complete = txList.get(wallet).isComplete; + return !complete; + } + return false; + } + + public static String getTxForSend(String wallet) + { + Map txList = GetTxList(); + if(txList.containsKey(wallet)) + { + return txList.get(wallet).TX; + } + return ""; + } +} diff --git a/app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java b/app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java new file mode 100644 index 0000000000..9408cb900e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LoadedWalletActivity.java @@ -0,0 +1,37 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + + +public class LoadedWalletActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_loaded_wallet); + MainActivity.commonInit(getApplicationContext()); + + if( getIntent().getExtras().containsKey(NfcAdapter.EXTRA_TAG) ) + { + Tag tag=getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null ) { + LoadedWalletActivityFragment fragment=(LoadedWalletActivityFragment)(getSupportFragmentManager().findFragmentById(R.id.loaded_wallet_fragment)); + fragment.onTagDiscovered(tag); + } + } + } + + @Override + public void onBackPressed() { + LoadedWalletActivityFragment loadedWalletActivityFragment=(LoadedWalletActivityFragment) getSupportFragmentManager().findFragmentById(R.id.loaded_wallet_fragment); + Intent data= loadedWalletActivityFragment.prepareResultIntent(); + data.putExtra("modification", "update"); + setResult(Activity.RESULT_OK, data); + finish(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java b/app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java new file mode 100644 index 0000000000..e001ac92af --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LoadedWalletActivityFragment.java @@ -0,0 +1,1599 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.text.Html; +import android.text.Spanned; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import static android.content.Context.CLIPBOARD_SERVICE; + +/** + * A placeholder fragment containing a simple view. + */ +public class LoadedWalletActivityFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + + private static final int REQUEST_CODE_SEND_PAYMENT = 1; + private static final int REQUEST_CODE_PURGE = 2; + private static final int REQUEST_CODE_REQUEST_PIN2_FOR_PURGE = 3; + private static final int REQUEST_CODE_VERIFY_CARD = 4; + private static final int REQUEST_CODE_ENTER_NEW_PIN = 5; + private static final int REQUEST_CODE_ENTER_NEW_PIN2 = 6; + private static final int REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN = 7; + private static final int REQUEST_CODE_SWAP_PIN = 8; + Tangem_Card mCard; + TextView tvCardID, tvBalance, tvOffline, tvBalanceEquivalent, tvWallet, tvInputs, lbInputs, tvOutputs, tvSend, tvPurge, tvError, tvMessage, tvIssuer, tvIssuerData, tvBlockchain, tvLastInput, tvLastOutput, lbLastOutput, tvValidationNode; + TextView tvHeader, tvCaution; + ImageView imgLookup; + TextView tvLookup; + ProgressBar progressBar; + ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion, ivQR; + SwipeRefreshLayout mSwipeRefreshLayout; + List updateTasks = new ArrayList<>(); + private NfcManager mNfcManager; + private final String logTag = "LoadedWalletFragment"; + private boolean lastReadSuccess = true; + private VerifyCardTask verifyCardTask = null; + private int requestPIN2Count = 0; + + public LoadedWalletActivityFragment() { + + } + + public void onRefresh() { + //Update + // Showing refresh animation before making http call + if (updateTasks.size() > 0) return; + ; + mSwipeRefreshLayout.setRefreshing(true); + mCard.clearInfo(); + mCard.setError(null); + mCard.setMessage(null); + + boolean needResendTX = LastSignStorage.getNeedTxSend(mCard.getWallet()); + + UpdateViews(); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(mCard); + int nodePort = engine.GetNextNodePort(mCard); + UpdateWalletInfoTask connectTaskEx = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(mCard.getWallet())); + } + + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + updateTasks.add(updateWalletInfoTask); + updateWalletInfoTask.execute(Electrum_Request.ListUnspent(mCard.getWallet()), Electrum_Request.ListHistory(mCard.getWallet())); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "bitcoin", "bitcoin"); + taskRate.execute(rate); + + + } else if (mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(mCard); + int nodePort = engine.GetNextNodePort(mCard); + UpdateWalletInfoTask connectTaskEx = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(mCard.getWallet())); + } + + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort, data); + + updateTasks.add(updateWalletInfoTask); + updateWalletInfoTask.execute(Electrum_Request.ListUnspent(mCard.getWallet()), Electrum_Request.ListHistory(mCard.getWallet())); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "bitcoin-cash", "bitcoin-cash"); + taskRate.execute(rate); + + + } + else if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet) { + ETHRequestTask updateETH = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetBalance(mCard.getWallet()); + reqETH.setID(67); + reqETH.setBlockchain(mCard.getBlockchain()); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(mCard.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(mCard.getBlockchain()); + + updateETH.execute(reqETH, reqNonce); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "ethereum", "ethereum"); + taskRate.execute(rate); + + } else if (mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask updateETH = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetTokenBalance(mCard.getWallet(), engine.GetContractAddress(mCard), engine.GetTokenDecimals(mCard)); + reqETH.setID(67); + reqETH.setBlockchain(mCard.getBlockchain()); + + Infura_Request reqBalance = Infura_Request.GetBalance(mCard.getWallet()); + reqBalance.setID(67); + reqBalance.setBlockchain(mCard.getBlockchain()); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(mCard.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(mCard.getBlockchain()); + updateETH.execute(reqETH, reqNonce, reqBalance); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(mCard.getWallet(), "basic-attention-token", "ethereum"); + taskRate.execute(rate); + } + + if (needResendTX) { + SendTransaction(LastSignStorage.getTxForSend(mCard.getWallet())); + } + } + + void doShareWallet(boolean useURI) { + if (useURI) { + String txtShare = CoinEngineFactory.Create(mCard.getBlockchain()).getShareWalletURI(mCard).toString(); + //String txtShare = Blockchain.getShareWalletURI(mCard).toString(); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, "Wallet address"); + intent.putExtra(Intent.EXTRA_TEXT, txtShare); + + PackageManager packageManager = getActivity().getPackageManager(); + List activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL); + boolean isIntentSafe = activities.size() > 0; + + if (isIntentSafe) { + String title = "Share wallet address with:"; + // Create intent to show chooser + Intent chooser = Intent.createChooser(intent, title); + // Verify the intent will resolve to at least one activity + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(chooser); + } + } else { + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(txtShare, txtShare)); + Toast.makeText(getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show(); + } + } else { + String txtShare = mCard.getWallet(); + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(txtShare, txtShare)); + Toast.makeText(getContext(), "Copied to clipboard", Toast.LENGTH_LONG).show(); + } + + + } + + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_loaded_wallet, container, false); + + mNfcManager = new NfcManager(this.getActivity(), this); + + + // SwipeRefreshLayout + mSwipeRefreshLayout = v.findViewById(R.id.swipe_container); + mSwipeRefreshLayout.setOnRefreshListener(this); + + mCard = new Tangem_Card(getActivity().getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getActivity().getIntent().getExtras().getBundle("Card")); + tvCardID = v.findViewById(R.id.tvCardID); + + tvBalance = v.findViewById(R.id.tvBalance); + tvOffline = v.findViewById(R.id.tvOffline); + + if (mCard.getBlockchain() == Blockchain.Token) { + //tvBalance.setLines(2); + tvBalance.setSingleLine(false); + } + + tvBalanceEquivalent = v.findViewById(R.id.tvBalanceEquivalent); + + tvWallet = v.findViewById(R.id.tvWallet); + tvWallet.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + doShareWallet(false); + } + }); + + tvInputs = v.findViewById(R.id.tvInputs); + lbInputs = v.findViewById(R.id.lbInputs); + + tvLastOutput = v.findViewById(R.id.tvLastOutput); + lbLastOutput = v.findViewById(R.id.lbLastOutput); + + final CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + boolean visibleFlag = engine != null ? engine.InOutPutVisible() : true; + int visibleIOPuts = visibleFlag ? View.VISIBLE : View.GONE; + if (tvInputs != null) { + tvInputs.setVisibility(visibleIOPuts); + } + + if (lbInputs != null) { + lbInputs.setVisibility(visibleIOPuts); + } + + if (tvLastOutput != null) { + tvLastOutput.setVisibility(visibleIOPuts); + } + + if (lbLastOutput != null) { + lbLastOutput.setVisibility(visibleIOPuts); + } + + tvValidationNode = v.findViewById(R.id.tvValidationNode); + + progressBar = v.findViewById(R.id.progressBar); + + tvBlockchain = v.findViewById(R.id.tvBlockchain); + ivBlockchain = v.findViewById(R.id.imgBlockchain); + ivPIN = v.findViewById(R.id.imgPIN); + ivPIN2orSecurityDelay = v.findViewById(R.id.imgPIN2orSecurityDelay); + ivDeveloperVersion = v.findViewById(R.id.imgDeveloperVersion); + + ivQR = v.findViewById(R.id.qrWallet); + + + try { + ivQR.setImageBitmap(generateQrCode(engine.getShareWalletURI(mCard).toString())); + } catch (WriterException e) { + e.printStackTrace(); + } + ivQR.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + doShareWallet(true); + } + }); + + tvError = v.findViewById(R.id.tvError); + tvMessage = v.findViewById(R.id.tvMessage); + + tvIssuer = v.findViewById(R.id.tvIssuer); + tvIssuerData = v.findViewById(R.id.tvIssuerData); + + tvHeader = v.findViewById(R.id.tvHeader); + tvCaution = v.findViewById(R.id.tvCaution); + + tvSend = v.findViewById(R.id.tvSend); + + imgLookup = v.findViewById(R.id.imgLookup); + + if (imgLookup != null) { + + imgLookup.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCard.hasBalanceInfo()) { + return; + } + CoinEngine engineClick = CoinEngineFactory.Create(mCard.getBlockchain()); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, engineClick.getShareWalletURIExplorer(mCard)); + startActivity(browserIntent); + } + }); + + } + + tvLookup = v.findViewById(R.id.tvLookup); + + if (tvLookup != null) { + + tvLookup.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCard.hasBalanceInfo()) { + return; + } + CoinEngine engineClick = CoinEngineFactory.Create(mCard.getBlockchain()); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, engineClick.getShareWalletURIExplorer(mCard)); + startActivity(browserIntent); + } + }); + } + + if (tvSend != null) { + tvSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mCard.hasBalanceInfo()) { + return; + } else if (!engine.IsBalanceNotZero(mCard)) { + Toast.makeText(getContext(), "The wallet is empty", Toast.LENGTH_LONG).show(); + return; + } else if (!engine.IsBalanceAlterNotZero(mCard)) { + Toast.makeText(getContext(), "Not enough funds for transaction fee (gas)!", Toast.LENGTH_LONG).show(); + return; + } else if (engine.AwaitingConfirmation(mCard)) { + Toast.makeText(getContext(), "Please wait while previous transaction is confirmed in blockchain", Toast.LENGTH_LONG).show(); + return; + } else if (!engine.CheckUnspentTransaction(mCard)) { + Toast.makeText(getContext(), "Please wait for confirmation of incoming transaction!", Toast.LENGTH_LONG).show(); + return; + } else if (mCard.getRemainingSignatures() == 0) { + Toast.makeText(getContext(), "Card hasn't remaining signature!", Toast.LENGTH_LONG).show(); + return; + } + + Intent intent = new Intent(getContext(), PreparePaymentActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT); + } + }); + } + + tvPurge = v.findViewById(R.id.tvPurge); + if (tvPurge != null) { + tvPurge.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showMenu(tvPurge); + } + } + ); + } + UpdateViews(); + + if (!mCard.hasBalanceInfo()) { + mSwipeRefreshLayout.setRefreshing(true); + mSwipeRefreshLayout.postDelayed(new Runnable() { + @Override + public void run() { + onRefresh(); + } + }, 1000); + } + return v; + } + + public void showMenu(View v) { + final PopupMenu popup = new PopupMenu(getActivity(), v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.menu_loaded_wallet, popup.getMenu()); + + popup.getMenu().findItem(R.id.action_set_PIN1).setVisible(mCard.allowSwapPIN()); + popup.getMenu().findItem(R.id.action_reset_PIN1).setVisible(mCard.allowSwapPIN() && !mCard.useDefaultPIN1()); + popup.getMenu().findItem(R.id.action_set_PIN2).setVisible(mCard.allowSwapPIN2()); + popup.getMenu().findItem(R.id.action_reset_PIN2).setVisible(mCard.allowSwapPIN2() && !mCard.useDefaultPIN2()); + popup.getMenu().findItem(R.id.action_reset_PINs).setVisible(mCard.allowSwapPIN() && mCard.allowSwapPIN2() && !mCard.useDefaultPIN1() && !mCard.useDefaultPIN2()); + + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + + int id = item.getItemId(); + switch (id) { + case R.id.action_set_PIN1: + doSetPin(); + return true; + case R.id.action_reset_PIN1: + doResetPin(); + return true; + case R.id.action_set_PIN2: + doSetPin2(); + return true; + case R.id.action_reset_PIN2: + doResetPin2(); + return true; + case R.id.action_reset_PINs: + doResetPins(); + return true; + case R.id.action_purge: + doPurge(); + return true; + default: + return false; + } + } + }); + popup.show(); + } + + String newPIN = "", newPIN2 = ""; + + void doSetPin() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestNewPIN.toString()); + newPIN = ""; + newPIN2 = ""; + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN); + } + + void doResetPin() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = PINStorage.getDefaultPIN(); + newPIN2 = ""; + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } + + void doResetPin2() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = ""; + newPIN2 = PINStorage.getDefaultPIN2(); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } + + void doResetPins() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = PINStorage.getDefaultPIN(); + newPIN2 = PINStorage.getDefaultPIN2(); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } + + + void doSetPin2() { + requestPIN2Count = 0; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestNewPIN2.toString()); + newPIN = ""; + newPIN2 = ""; + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN2); + } + + void doPurge() { + requestPIN2Count = 0; + final CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + if (!mCard.hasBalanceInfo()) { + return; + } else if (engine.IsBalanceNotZero(mCard)) { + Toast.makeText(getContext(), "Cannot erase wallet with non-zero balance", Toast.LENGTH_LONG).show(); + return; + } + + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_PURGE); + } + + void UpdateViews() { + try { + if (timerHideErrorAndMessage != null) { + timerHideErrorAndMessage.cancel(); + timerHideErrorAndMessage = null; + } + tvCardID.setText(mCard.getCIDDescription()); + + if ((mCard.getError() == null || mCard.getError().isEmpty())) { + tvError.setVisibility(View.GONE); + tvError.setText(""); + } else { + tvError.setVisibility(View.VISIBLE); + tvError.setText(mCard.getError()); + } + + boolean needResendTX = LastSignStorage.getNeedTxSend(mCard.getWallet()); + + if ((mCard.getMessage() == null || mCard.getMessage().isEmpty()) && !needResendTX) { + tvMessage.setText(""); + tvMessage.setVisibility(View.GONE); + } else { + if (needResendTX) { + tvMessage.setText("Sending cached transaction..."); + } else { + tvMessage.setText(mCard.getMessage()); + } + tvMessage.setVisibility(View.VISIBLE); + + } + + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (engine.HasBalanceInfo(mCard) || mCard.getOfflineBalance() == null) { + if (mCard.getBlockchain() == Blockchain.Token) { + Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard)); + tvBalance.setText(html); + } else { + tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + } + + tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard)); + tvOffline.setVisibility(View.INVISIBLE); + } else { + String offlineAmount = engine.ConvertByteArrayToAmount(mCard, mCard.getOfflineBalance()); + if (mCard.getBlockchain() == Blockchain.Token) { + tvBalance.setText("NOT IMPLEMENTED"); + } else { + tvBalance.setText(engine.GetAmountDescription(mCard, offlineAmount)); + } + + tvBalanceEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, offlineAmount)); + tvOffline.setVisibility(View.VISIBLE); + } + + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + //tvBalanceEquivalent.setError("Service unavailable"); + } else { + tvBalanceEquivalent.setError(null); + } + + tvWallet.setText(mCard.getWallet()); + + tvInputs.setText(mCard.getInputsDescription()); + if (mCard.getLastInputDescription().contains("awaiting")) { + tvInputs.setTextColor(getContext().getResources().getColor(R.color.not_confirmed, getContext().getTheme())); + } else if (mCard.getLastInputDescription().contains("None")) { + tvInputs.setTextColor(getContext().getResources().getColor(R.color.primary_dark, getContext().getTheme())); + } else { + tvInputs.setTextColor(getContext().getResources().getColor(R.color.confirmed, getContext().getTheme())); + } + + if (tvOutputs != null) { + tvOutputs.setText(mCard.getOutputsDescription()); + } + + if (tvLastInput != null) { + tvLastInput.setText(mCard.getLastInputDescription()); + } + if (tvLastOutput != null) { + tvLastOutput.setText(mCard.getLastOutputDescription()); + } + + tvBlockchain.setText(mCard.getBlockchainName()); + ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this.getContext(), mCard.getTokenSymbol())); + + if (tvValidationNode != null) { + tvValidationNode.setText(mCard.getValidationNodeDescription()); + } + + if (mCard.useDefaultPIN1()) { + ivPIN.setImageResource(R.drawable.unlock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN.setImageResource(R.drawable.lock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } + + if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.timer); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show(); + } + }); + + } else if (mCard.useDefaultPIN2()) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } + + + if (mCard.useDevelopersFirmware()) { + ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version); + ivDeveloperVersion.setVisibility(View.VISIBLE); + ivDeveloperVersion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivDeveloperVersion.setVisibility(View.INVISIBLE); + } + + if (tvSend != null) { + if (mCard.hasBalanceInfo()) { + tvSend.setEnabled(true); + } else { + tvSend.setEnabled(false); + } + } + + if (tvPurge != null) { + if (mCard.hasBalanceInfo()) { + tvPurge.setEnabled(true); + } else { + tvPurge.setEnabled(false); + } + } + + tvIssuer.setText(mCard.getIssuerDescription()); + + timerHideErrorAndMessage = new Timer(); + + + timerHideErrorAndMessage.schedule(new TimerTask() { + @Override + public void run() { + tvError.post(new Runnable() { + @Override + public void run() { + tvMessage.setVisibility(View.GONE); + tvError.setVisibility(View.GONE); + mCard.setError(null); + mCard.setMessage(null); + } + }); + } + }, 5000); + + if (mCard.isReusable()) { + tvHeader.setText("REUSABLE WALLET"); + tvCaution.setVisibility(View.GONE); + } else { + if (mCard.getMaxSignatures() == mCard.getRemainingSignatures()) { + tvHeader.setText("BANKNOTE"); + tvCaution.setVisibility(View.GONE); + } else { + tvHeader.setText("NON-TRANSFERABLE BANKNOTE"); + tvCaution.setVisibility(View.VISIBLE); + } + } + + if (mCard.useDevelopersFirmware()) { + tvHeader.setText("DEVELOPER KIT"); + tvCaution.setVisibility(View.VISIBLE); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + Timer timerHideErrorAndMessage = null; + + public Intent prepareResultIntent() { + Intent data = new Intent(); + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + return data; + } + + private void SendTransaction(String tx) { + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request req = Infura_Request.SendTransaction(mCard.getWallet(), tx); + req.setID(67); + req.setBlockchain(mCard.getBlockchain()); + task.execute(req); + } else if (mCard.getBlockchain() == Blockchain.Bitcoin ||mCard.getBlockchain() == Blockchain.BitcoinTestNet) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + + UpdateWalletInfoTask connectTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + else if (mCard.getBlockchain() == Blockchain.BitcoinCash ||mCard.getBlockchain() == Blockchain.BitcoinCashTestNet) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + + UpdateWalletInfoTask connectTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_CODE_ENTER_NEW_PIN: + if (resultCode == Activity.RESULT_OK) { + if (data != null) { + if (data.getExtras().containsKey("confirmPIN")) { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN = data.getStringExtra("newPIN"); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } else { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("newPIN", data.getStringExtra("newPIN")); + intent.putExtra("mode", RequestPINActivity.Mode.ConfirmNewPIN.toString()); + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN); + } + } + } + break; + case REQUEST_CODE_ENTER_NEW_PIN2: + if (resultCode == Activity.RESULT_OK) { + if (data != null) { + if (data.getExtras().containsKey("confirmPIN2")) { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + newPIN2 = data.getStringExtra("newPIN2"); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + } else { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("newPIN2", data.getStringExtra("newPIN2")); + intent.putExtra("mode", RequestPINActivity.Mode.ConfirmNewPIN2.toString()); + startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN2); + } + } + } + break; + case REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN: + if (resultCode == Activity.RESULT_OK) { + if (newPIN.equals("")) { + newPIN = mCard.getPIN(); + } + if (newPIN2.equals("")) { + newPIN2 = PINStorage.getPIN2(); + } + + PINSwapWarningDialog dialog = (new PINSwapWarningDialog()); + dialog.activityFragment = this; + if (!PINStorage.isDefaultPIN(newPIN) || !PINStorage.isDefaultPIN2(newPIN2)) { + dialog.message = "If you forget your new PIN you will lose your money forever!"; + } else { + dialog.message = "If you use default PIN someone can steal your money!"; + } + dialog.show(getActivity().getFragmentManager(), "PINSwapWarningDialog"); + } + break; + + case REQUEST_CODE_SWAP_PIN: + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + data = new Intent(); + + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + data.putExtra("modification", "delete"); + } else { + data.putExtra("modification", "update"); + } + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } else { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN); + return; + } else { + if (data != null && data.getExtras().containsKey("message")) { + mCard.setError(data.getStringExtra("message")); + } + } + } + break; + case REQUEST_CODE_REQUEST_PIN2_FOR_PURGE: + if (resultCode == Activity.RESULT_OK) { + Intent intent = new Intent(getContext(), PurgeActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_PURGE); + } + break; + case REQUEST_CODE_PURGE: + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + data = new Intent(); + + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + data.putExtra("modification", "delete"); + } else { + data.putExtra("modification", "update"); + } + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } else { + if (data != null && data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) { + requestPIN2Count++; + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN2.toString()); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_PURGE); + return; + } else { + if (data != null && data.getExtras().containsKey("message")) { + mCard.setError(data.getStringExtra("message")); + } + } + UpdateViews(); + } + break; + case REQUEST_CODE_SEND_PAYMENT: + if (resultCode == Activity.RESULT_OK) { + mSwipeRefreshLayout.postDelayed(new Runnable() { + @Override + public void run() { + onRefresh(); + } + }, 10000); + mSwipeRefreshLayout.setRefreshing(true); + mCard.clearInfo(); + UpdateViews(); + } + + if (data != null) { + if (data.getExtras().containsKey("UID") && data.getExtras().containsKey("Card")) { + Tangem_Card updatedCard = new Tangem_Card(data.getStringExtra("UID")); + updatedCard.LoadFromBundle(data.getBundleExtra("Card")); + mCard = updatedCard; + } + if (data.getExtras().containsKey("message")) { + if (resultCode == Activity.RESULT_OK) { + mCard.setMessage(data.getStringExtra("message")); + } else { + mCard.setError(data.getStringExtra("message")); + } + } + UpdateViews(); + } + + break; + } + + } + + private void startSwapPINActivity() { + Intent intent = new Intent(getContext(), SwapPINActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + intent.putExtra("newPIN", newPIN); + intent.putExtra("newPIN2", newPIN2); + startActivityForResult(intent, REQUEST_CODE_SWAP_PIN); + } + + public static Bitmap generateQrCode(String myCodeText) throws WriterException { + Hashtable hintMap = new Hashtable(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + + int size = 256; + + BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hintMap); + int width = bitMatrix.getWidth(); + Bitmap bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565); + for (int x = 0; x < width; x++) { + for (int y = 0; y < width; y++) { + bmp.setPixel(y, x, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE); + } + } + return bmp; + } + + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + for (UpdateWalletInfoTask ut : updateTasks) { + ut.cancel(true); + } + mNfcManager.onStop(); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + if (!mCard.getUID().equals(sUID)) { + Log.d(logTag, "Invalid UID: " + sUID); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } else { + Log.v(logTag, "UID: " + sUID); + } + + if (lastReadSuccess) { + isoDep.setTimeout(1000); + } else { + isoDep.setTimeout(65000); + } + //lastTag = tag; + verifyCardTask = new VerifyCardTask(getContext(), mCard, mNfcManager, isoDep, this); + verifyCardTask.start(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void ErrorOnUpdate(String message) { + mCard.setError("Cannot obtain data from blockchain"); + UpdateViews(); + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain) { + super(blockchain); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + if (request.error == null) { + + if (request.isMethod(Infura_Request.METHOD_ETH_GetBalance)) { + try { + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + BigInteger d = l.divide(new BigInteger("1000000000000000000", 10)); + Long balance = d.longValue(); + + mCard.setBalanceConfirmed(balance); + mCard.setBalanceUnconfirmed(0L); + if (mCard.getBlockchain() != Blockchain.Token) + mCard.setDecimalBalance(l.toString(10)); + mCard.setDecimalBalanceAlter(l.toString(10)); + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_Call)) { + try { + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + Long balance = l.longValue(); + + if (l.compareTo(BigInteger.ZERO) == 0) { + mCard.setBlockchainID(Blockchain.Ethereum.getID()); + mCard.addTokenToBlockchainName(); + mSwipeRefreshLayout.setRefreshing(false); + onRefresh(); + return; + } + + mCard.setBalanceConfirmed(balance); + mCard.setBalanceUnconfirmed(0L); + mCard.setDecimalBalance(l.toString(10)); + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_GetOutTransactionCount)) { + try { + String nonce = request.getResultString(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + + mCard.SetConfirmTXCount(count); + } catch (JSONException e) { + e.printStackTrace(); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_SendRawTransaction)) { + try { + String hashTX = ""; + + try { + String tmp = request.getResultString(); + hashTX = tmp; + } catch (JSONException e) { + JSONObject msg = request.getAnswer(); + JSONObject err = msg.getJSONObject("error"); + hashTX = err.getString("message"); + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + ErrorOnUpdate("Failed to send transaction. Try again"); + return; + } + + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + Log.e("TX_RESULT", hashTX); + + + BigInteger nonce = mCard.GetConfirmTXCount(); + nonce.add(BigInteger.valueOf(1)); + mCard.SetConfirmTXCount(nonce); + Log.e("TX_RESULT", hashTX); + + } catch (Exception e) { + e.printStackTrace(); + ErrorOnUpdate("Failed to send transaction. Try again"); + } + } + UpdateViews(); + } else { + ErrorOnUpdate(request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } + + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + } + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + verifyCardTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(getContext(), VerifyCardActivity.class); + // TODO обновить карту mCard + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + startActivityForResult(intent, REQUEST_CODE_VERIFY_CARD); + //addCard(cardProtocol.getCard()); + } + }); + } else { + // remove last UIDs because of error and no card read + progressBar.post(new Runnable() { + @Override + public void run() { + lastReadSuccess = false; + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getActivity().getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + verifyCardTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadWait(int msec) { + WaitSecurityDelayDialog.OnReadWait(getActivity(), msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(getActivity(), timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(getActivity()); + } + + + private class RateInfoTask extends ExchangeTask { + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (ExchangeRequest request : requests) { + if (request.error == null) { + try { + + JSONArray arr = request.getAnswerList(); + for (int i = 0; i < arr.length(); ++i) { + JSONObject obj = arr.getJSONObject(i); + String currency = obj.getString("id"); + + boolean stop = false; + boolean stopAlter = false; + if (currency.equals(request.currency)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCard.setRate(rate); + UpdateViews(); + stop = true; + } + + if (currency.equals(request.currencyAlter)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCard.setRateAlter(rate); + UpdateViews(); + stopAlter = true; + } + + if (stop && stopAlter) { + break; + } + + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + } + } + + private class UpdateWalletInfoTask extends Electrum_Task { + public UpdateWalletInfoTask(String host, int port) { + super(host, port); + } + + + public UpdateWalletInfoTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onCancelled() { + super.onCancelled(); + updateTasks.remove(this); + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + Log.i("RequestWalletInfoTask", "onPostExecute[" + String.valueOf(updateTasks.size()) + "]"); + updateTasks.remove(this); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + for (Electrum_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_GetBalance)) { + try { + String mWalletAddress = request.getParams().getString(0); + Long confBalance = request.getResult().getLong("confirmed"); + Long unconf = request.getResult().getLong("unconfirmed"); + if (sharedCounter != null) { + int counter = sharedCounter.requestCounter.incrementAndGet(); + if (counter != 1) { + continue; + } + } + + mCard.setBalanceConfirmed(confBalance); + mCard.setBalanceUnconfirmed(unconf); + mCard.setDecimalBalance(String.valueOf(confBalance)); + mCard.setValidationNodeDescription(getValidationNodeDescription()); + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } else { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } + } else if (request.isMethod(Electrum_Request.METHOD_SendTransaction)) { + try { + String hashTX = request.getResultString(); + + try { + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + Log.e("TX_RESULT", hashTX); + + } catch (Exception e) { + engine.SwitchNode(mCard); + ErrorOnUpdate("Failed to send transaction. Try again."); + } + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate("Failed to send transaction. Try again."); + engine.SwitchNode(mCard); + } + } else if (request.isMethod(Electrum_Request.METHOD_ListUnspent)) { + try { + String mWalletAddress = request.getParams().getString(0); + + JSONArray jsUnspentArray = request.getResultArray(); + try { + mCard.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Tangem_Card.UnspentTransaction trUnspent = new Tangem_Card.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + mCard.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + String nodeAddress = engine.GetNextNode(mCard); + int nodePort = engine.GetNextNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + + updateTasks.add(updateWalletInfoTask); + + updateWalletInfoTask.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHistory)) { + try { + String mWalletAddress = request.getParams().getString(0); + + JSONArray jsHistoryArray = request.getResultArray(); + try { + mCard.getHistoryTransactions().clear(); + for (int i = 0; i < jsHistoryArray.length(); i++) { + JSONObject jsUnspent = jsHistoryArray.getJSONObject(i); + Tangem_Card.HistoryTransaction trHistory = new Tangem_Card.HistoryTransaction(); + trHistory.txID = jsUnspent.getString("tx_hash"); + trHistory.Height = jsUnspent.getInt("height"); + mCard.getHistoryTransactions().add(trHistory); + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + + for (int i = 0; i < jsHistoryArray.length(); i++) { + JSONObject jsUnspent = jsHistoryArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + UpdateWalletInfoTask updateWalletInfoTask = new UpdateWalletInfoTask(nodeAddress, nodePort); + updateTasks.add(updateWalletInfoTask); + + updateWalletInfoTask.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + + } + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHeader)) { + try { + JSONObject jsHeader = request.getResult(); + try { + mCard.getHaedersInfo(); + mCard.UpdateHeaderInfo(new Tangem_Card.HeaderInfo( + jsHeader.getInt("block_height"), + jsHeader.getInt("timestamp"))); + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + + } + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + + } + } else if (request.isMethod(Electrum_Request.METHOD_GetTransaction)) { + try { + + String txHash = request.TxHash; + String raw = request.getResultString(); + + List listTx = mCard.getUnspentTransactions(); + for (Tangem_Card.UnspentTransaction tx : listTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + } + } + + List listHTx = mCard.getHistoryTransactions(); + for (Tangem_Card.HistoryTransaction tx : listHTx) { + if (tx.txID.equals(txHash)) { + tx.Raw = raw; + try { + ArrayList prevHashes = BTCUtils.getPrevTX(raw); + + boolean isOur = false; + for (byte[] hash : prevHashes) { + String checkID = BTCUtils.toHex(hash); + for (Tangem_Card.HistoryTransaction txForCheck : listHTx) { + if (txForCheck.txID == checkID) { + isOur = true; + } + } + } + + tx.isInput = !isOur; + } catch (BitcoinException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + Log.e("TX", raw); + } + } + + } catch (JSONException e) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + engine.SwitchNode(mCard); + } + } + UpdateViews(); + } else { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + ErrorOnUpdate(request.error); + engine.SwitchNode(mCard); + + } + } else { + ErrorOnUpdate(request.error); + engine.SwitchNode(mCard); + + } + + } + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } else { + e.printStackTrace(); + ErrorOnUpdate(e.toString()); + } + } + } + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + + } + } + + public static class PINSwapWarningDialog extends DialogFragment { + + LoadedWalletActivityFragment activityFragment = null; + String message; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Your money is at risk!") + .setMessage(message) + .setCancelable(true) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + PINSwapWarningDialog.this.dismiss(); + } + }) + .setPositiveButton("Continue", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + if (activityFragment != null) + activityFragment.startSwapPINActivity(); + } + } + ) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/LogFileProvider.java b/app/src/main/java/com/tangem/wallet/LogFileProvider.java new file mode 100644 index 0000000000..a4f710d5fc --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LogFileProvider.java @@ -0,0 +1,108 @@ +package com.tangem.wallet; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; + +/** + * Created by dvol on 15.02.2018. + */ + +public class LogFileProvider extends ContentProvider { + + private static final String CLASS_NAME = "LogFileProvider"; + + // The authority is the symbolic name for the provider class + public static final String AUTHORITY = "com.tangem.wallet.LogFileProvider"; + + // UriMatcher used to match against incoming requests + private UriMatcher uriMatcher; + + @Override + public boolean onCreate() { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + // Add a URI to the matcher which will match against the form + // 'content://it.my.app.LogFileProvider/*' + // and return 1 in the case that the incoming Uri matches this pattern + uriMatcher.addURI(AUTHORITY, "*", 1); + + return true; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) + throws FileNotFoundException { + + String LOG_TAG = CLASS_NAME+"-oF"; + + Log.v(LOG_TAG, + "Called with uri: '" + uri + "'." + uri.getLastPathSegment()); + + // Check incoming Uri against the matcher + switch (uriMatcher.match(uri)) { + + // If it returns 1 - then it matches the Uri defined in onCreate + case 1: + + // The desired file name is specified by the last segment of the + // path + // E.g. + // 'content://it.my.app.LogFileProvider/Test.txt' + // Take this and build the path to the file + String fileLocation = getContext().getCacheDir() + File.separator + + uri.getLastPathSegment(); + + // Create & return a ParcelFileDescriptor pointing to the file + // Note: I don't care what mode they ask for - they're only getting + // read only + ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File( + fileLocation), ParcelFileDescriptor.MODE_READ_ONLY); + return pfd; + + // Otherwise unrecognised Uri + default: + Log.v(LOG_TAG, "Unsupported uri: '" + uri + "'."); + throw new FileNotFoundException("Unsupported uri: " + + uri.toString()); + } + } + + // ////////////////////////////////////////////////////////////// + // Not supported / used / required for this example + // ////////////////////////////////////////////////////////////// + + @Override + public int update(Uri uri, ContentValues contentvalues, String s, + String[] as) { + return 0; + } + + @Override + public int delete(Uri uri, String s, String[] as) { + return 0; + } + + @Override + public Uri insert(Uri uri, ContentValues contentvalues) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Cursor query(Uri uri, String[] projection, String s, String[] as1, + String s1) { + return null; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Logger.java b/app/src/main/java/com/tangem/wallet/Logger.java new file mode 100644 index 0000000000..7b2d67f28c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Logger.java @@ -0,0 +1,285 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.util.Log; + +import com.tangem.cardReader.Util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Date; + +public class Logger { + + public static File collectLogs(Context context) { + File f = new File(context.getCacheDir().getAbsolutePath() + "/Wallet_" + Util.formatDateTimeToFileName(new Date()) + ".log"); + try { + if (f.createNewFile()) { + f.setReadable(true); + FileWriter fileWriter = new FileWriter(f, true); + Process process = Runtime.getRuntime().exec("logcat -d -b main -v time"); + try { + InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader bufferedReader = new BufferedReader(isr); + BufferedWriter buf = new BufferedWriter(fileWriter); + buf.append("Tangem Wallet logs"); + buf.newLine(); + + int i = 0; + String line; + while ((line = bufferedReader.readLine()) != null) { + buf.append(line); + buf.newLine(); + i++; + } + Log.e("Logger", String.format("%d log lines collected", i)); + buf.newLine(); + buf.flush(); + buf.close(); + + } finally { + process.destroy(); + } + + return f; + } + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + +} + +//public class Logger { +// +// public static File[] getLastLogFiles() { +// File path = new File(Environment.getExternalStorageDirectory(), "Tangem/logs"); +// if (!path.exists()) { +// return null; +// } +// File[] files = path.listFiles(); +// Arrays.sort(files, new Comparator() { +// @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 +// ); +// } +// } +// +//} + diff --git a/app/src/main/java/com/tangem/wallet/LogoActivity.java b/app/src/main/java/com/tangem/wallet/LogoActivity.java new file mode 100644 index 0000000000..913af1e0f0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/LogoActivity.java @@ -0,0 +1,68 @@ +package com.tangem.wallet; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * An example full-screen activity that shows and hides the system UI (i.e. + * status bar and navigation/system bar) with user interaction. + */ +public class LogoActivity extends AppCompatActivity { + + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hide(); + } + }; + + ImageView imgLogo; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_logo); + + imgLogo= (ImageView) findViewById(R.id.imgLogo); + // Set up the user interaction to manually show or hide the system UI. + imgLogo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) {hide(); + } + }); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + TextView AppVersion = (TextView) findViewById(R.id.AppVersion); + AppVersion.setText("BETA v." + BuildConfig.VERSION_NAME); + if( !getIntent().getBooleanExtra("skipAutoHide",false)) { + delayedHide(1000); + } + } + + private void hide() { + Intent intent=new Intent(getBaseContext(),MainActivity.class); + startActivity(intent); + finish(); + } + + /** + * Schedules a call to hide() in [delay] milliseconds, canceling any + * previously scheduled calls. + */ + private void delayedHide(int delayMillis) { + //imgLogo.removeCallbacks(mHideRunnable); + imgLogo.postDelayed(mHideRunnable, delayMillis); + } +} diff --git a/app/src/main/java/com/tangem/wallet/MainActivity.java b/app/src/main/java/com/tangem/wallet/MainActivity.java new file mode 100644 index 0000000000..c65f71fd97 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/MainActivity.java @@ -0,0 +1,404 @@ +package com.tangem.wallet; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.PopupMenu; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Transformation; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.scottyab.rootbeer.RootBeer; +import com.skyfishjy.library.RippleBackground; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + + +public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener { + + public static final int DIALOG_ENABLE_INTERNET = 1; + private static final int REQUEST_CODE_SEND_EMAIL = 2; + private String logTag = "MainActivity"; + + public interface OnCardsClean { + void doClean(); + } + +// public interface OnCreateNFCDialog { +// Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li); +// } + + OnCardsClean onCardsClean; +// OnCreateNFCDialog onCreateNFCDialog; + NfcAdapter.ReaderCallback onNFCReaderCallback; + FloatingActionButton fab; + + + public void setOnCardsClean(OnCardsClean onCardsClean) { + this.onCardsClean = onCardsClean; + } + +// public void setOnCreateNFCDialog(OnCreateNFCDialog onCreateNFCDialog) { +// this.onCreateNFCDialog = onCreateNFCDialog; +// } + + public void setNfcAdapterReaderCallback(NfcAdapter.ReaderCallback callback) { + this.onNFCReaderCallback = callback; + } + + public void showCleanButton() { + + findViewById(R.id.tvTapPrompt).setVisibility(View.INVISIBLE); + } + + public void hideCleanButton() { + findViewById(R.id.tvTapPrompt).setVisibility(View.VISIBLE); + } + + public static class RootFoundDialog extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Your Android device is rooted. Security at risk!") + .setCancelable(false) + .setPositiveButton("Got it", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + } + } + ) + .create(); + } + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + RootBeer rootBeer = new RootBeer(this); + if (rootBeer.isRootedWithoutBusyBoxCheck()) { + //we found indication of root + new RootFoundDialog().show(getFragmentManager(), "RootFoundDialog"); + } + + setContentView(R.layout.activity_main); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); + + commonInit(getApplicationContext()); + + TextView tvNFCHint = findViewById(R.id.tvNFCHint); + if(tvNFCHint != null) + { +// tvNFCHint.setText("Scan a banknote with your\n" + PhoneUtility.GetPhoneName() + "\nas shown above"); + tvNFCHint.setText("Scan a banknote with your\n smartphone as shown above"); + } + + DeviceNFCAntennaLocation antenna = new DeviceNFCAntennaLocation(); + antenna.getAntennaLocation(); + final LinearLayout hand = findViewById(R.id.llHand); + final LinearLayout nfc = findViewById(R.id.llNFC); + final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) hand.getLayoutParams(); + final RelativeLayout.LayoutParams lp2 = (RelativeLayout.LayoutParams) nfc.getLayoutParams(); + final float dp = getResources().getDisplayMetrics().density; + final float lm = dp*(69 + antenna.X * 75); + lp.topMargin = (int) (dp*(-100 + antenna.Y * 250)); + lp2.topMargin = (int) (dp*(-125 + antenna.Y * 250)); + nfc.setLayoutParams(lp2); + + Animation a = new Animation() { + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + lp.leftMargin = (int)(lm * interpolatedTime); + hand.setLayoutParams(lp); + } + }; + a.setDuration(2000); // in ms + a.setInterpolator(new DecelerateInterpolator()); + hand.startAnimation(a); + + fab = findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + showMenu(view); + } + }); + + MainActivityFragment mainActivityFragment=(MainActivityFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentMain); + if( mainActivityFragment.getCardListAdapter().getItemCount()>0 ) + { + showCleanButton(); + }else { + hideCleanButton(); + } + + final RippleBackground rippleBackground=(RippleBackground)findViewById(R.id.imNFC); + rippleBackground.startRippleAnimation(); + + + Intent intent = getIntent(); + if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))) { + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null && onNFCReaderCallback != null) { + onNFCReaderCallback.onTagDiscovered(tag); + } + } + } + + public static void commonInit(Context context) + { + if( PINStorage.needInit() ) { + PINStorage.Init(context); + } + if( LastSignStorage.needInit() ) { + LastSignStorage.Init(context); + } + } + + @Override + protected void onDestroy() { +// Logger.StopSaveToFile(getApplicationContext()); + super.onDestroy(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))) { + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag != null && onNFCReaderCallback != null) { + onNFCReaderCallback.onTagDiscovered(tag); + } + } + + } + + @Override + public boolean onKeyDown(int keycode, KeyEvent e) { + switch (keycode) { + case KeyEvent.KEYCODE_MENU: + fab.requestFocus(); + showMenu(fab); + return true; + } + + return super.onKeyDown(keycode, e); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + if( BuildConfig.DEBUG ) { + for(int i=0; i 0) { + String[] fileNames = new String[filelocations.length]; + for (int i = 0; i < filelocations.length; i++) + fileNames[i] = filelocations[i].getAbsolutePath(); + zipFile = File.createTempFile("tangemLogs", ".zip", filelocations[0].getParentFile()); + Compress compress = new Compress(fileNames, zipFile.getAbsolutePath()); + compress.zip(); + Log.e(logTag, String.format("Send %d bytes zip with logs", zipFile.length())); + Uri attachment = Uri.parse("content://" + LogFileProvider.AUTHORITY + "/" + + zipFile.getName()); + + intent.putExtra(Intent.EXTRA_STREAM, attachment); + zipFile.deleteOnExit(); + } + + List activities = getPackageManager().queryIntentActivities(intent, 0); + boolean isIntentSafe = activities.size() > 0; + + if (isIntentSafe) { + startActivity(intent); + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_SEND_EMAIL) { + if (zipFile != null) { + zipFile.delete(); + zipFile = null; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + public void showMenu(View v) { + PopupMenu popup = new PopupMenu(this, v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.menu_main, popup.getMenu()); + if( BuildConfig.DEBUG ) { + for(int i=0; i slCardUIDs = new ArrayList<>(); + private static final String logTag = "MainActivityFragment"; + private ProgressBar progressBar; + + private CardListAdapter mCardListAdapter; + + public CardListAdapter getCardListAdapter() { + return mCardListAdapter; + } + + private ReadCardInfoTask readCardInfoTask; + private SwipeRefreshLayout mSwipeRefreshLayout; //TODO: tmp + List requestTasks = new ArrayList<>(); + + public MainActivityFragment() { + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mCardListAdapter.onSaveInstanceState(outState); + outState.putStringArrayList("slCardUIDs", slCardUIDs); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View result = inflater.inflate(R.layout.fragment_main, container, false); + + mNfcManager = new NfcManager(this.getActivity(), this); + verifyPermissions(); + + progressBar = result.findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + RecyclerView rvCards = result.findViewById(R.id.lvCards); + rvCards.setLayoutManager(new LinearLayoutManager(getContext())); + mCardListAdapter = new CardListAdapter(getActivity().getLayoutInflater(), savedInstanceState, this); + rvCards.setAdapter(mCardListAdapter); + if (savedInstanceState != null && savedInstanceState.containsKey("slCardUIDs")) { + slCardUIDs = savedInstanceState.getStringArrayList("slCardUIDs"); + } + + // SwipeRefreshLayout + mSwipeRefreshLayout = result.findViewById(R.id.swipe_container); //TODO: tmp + + if( mSwipeRefreshLayout!=null ) { + SwipeRefreshLayout.OnRefreshListener onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() { + + @Override + public void onRefresh() { + //Update + // Showing refresh animation before making http call + + if (requestTasks.size() > 0) return; + + if (requestTasks.size() > 0) { //TODO: tmp + mSwipeRefreshLayout.setRefreshing(true); + } else { + mSwipeRefreshLayout.setRefreshing(false); + } + } + }; + mSwipeRefreshLayout.setOnRefreshListener(onRefreshListener); //TODO: tmp + } + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + //Remove swiped item from list and notify the RecyclerView + int cardIndex = viewHolder.getAdapterPosition(); + if (cardIndex < 0 || cardIndex >= mCardListAdapter.getItemCount()) return; + slCardUIDs.remove(mCardListAdapter.getCard(cardIndex).getUID()); + if (mCardListAdapter.getCard(cardIndex).getUID() == lastRead_UID) { + lastRead_UID = ""; + } + mCardListAdapter.removeCard(cardIndex); + if (mCardListAdapter.getItemCount() == 0 && getActivity().getClass() == MainActivity.class) { + ((MainActivity) getActivity()).hideCleanButton(); + } + } + }); + + itemTouchHelper.attachToRecyclerView(rvCards); + + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).setOnCardsClean(this); +// ((MainActivity) getActivity()).setOnCreateNFCDialog(this); + ((MainActivity) getActivity()).setNfcAdapterReaderCallback(this); + } + + return result; + } + + private void verifyPermissions() { + NfcManager.verifyPermissions(getActivity()); + if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + Log.e("QRScanActivity", "User hasn't granted permission to use camera"); + ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS); + } + } + + int unsuccessReadCount = 0; + Tag lastTag = null; + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + if (slCardUIDs.indexOf(sUID) != -1) { + Log.d(logTag, "Repeat UID: " + sUID); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } else { + Log.v(logTag, "UID: " + sUID); + } + +// Log.e(logTag,"setTimeout("+String.valueOf(1000 + 3000 * unsuccessReadCount)+")"); + if (unsuccessReadCount < 2) { + isoDep.setTimeout(2000 + 5000 * unsuccessReadCount); + } else { + isoDep.setTimeout(90000); + } + lastTag = tag; + + readCardInfoTask = new ReadCardInfoTask(isoDep, this); + readCardInfoTask.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onResume() { + super.onResume(); + + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (readCardInfoTask != null) { + readCardInfoTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (readCardInfoTask != null) { + readCardInfoTask.cancel(true); + } + for (RequestWalletInfoTask rt : requestTasks) { + rt.cancel(true); + } + super.onStop(); + } + + public void refreshCard(Tangem_Card card) { + CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain()); + if (card.getBlockchain() == Blockchain.Ethereum || card.getBlockchain() == Blockchain.EthereumTestNet) { + ETHRequestTask task = new ETHRequestTask(card.getBlockchain()); + Infura_Request req = Infura_Request.GetBalance(card.getWallet()); + req.setID(67); + req.setBlockchain(card.getBlockchain()); + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + task.execute(req, reqNonce); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "ethereum", "ethereum"); + taskRate.execute(rate); + + } else if (card.getBlockchain() == Blockchain.BitcoinTestNet || card.getBlockchain() == Blockchain.Bitcoin) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin", "bitcoin"); + taskRate.execute(rate); + + } + else if (card.getBlockchain() == Blockchain.BitcoinCashTestNet || card.getBlockchain() == Blockchain.BitcoinCash) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + for (int i = 0; i < data.allRequest; ++i) { + + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin-cash", "bitcoin-cash"); + taskRate.execute(rate); + + }else if (card.getBlockchain() == Blockchain.Token) { + ETHRequestTask updateETH = new ETHRequestTask(card.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetTokenBalance(card.getWallet(), engine.GetContractAddress(card), engine.GetTokenDecimals(card)); + reqETH.setID(67); + reqETH.setBlockchain(card.getBlockchain()); + + + Infura_Request reqBalance = Infura_Request.GetBalance(card.getWallet()); + reqBalance.setID(67); + reqBalance.setBlockchain(card.getBlockchain()); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "basic-attention-token", "ethereum"); + taskRate.execute(rate); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + updateETH.execute(reqETH, reqNonce, reqBalance); + } + } + + public void addCard(final Tangem_Card card) { + if (mCardListAdapter != null) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + unsuccessReadCount = 0; + slCardUIDs.add(0, card.getUID()); + mCardListAdapter.addCard(card); + + CoinEngine engine = CoinEngineFactory.Create(card.getBlockchain()); + + if (card.getStatus() == Tangem_Card.Status.Loaded) { + + if (card.getBlockchain() == Blockchain.Ethereum || card.getBlockchain() == Blockchain.EthereumTestNet) { + ETHRequestTask task = new ETHRequestTask(card.getBlockchain()); + Infura_Request req = Infura_Request.GetBalance(card.getWallet()); + req.setID(67); + req.setBlockchain(card.getBlockchain()); + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + task.execute(req, reqNonce); + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "ethereum", "ethereum"); + taskRate.execute(rate); + + } else if (card.getBlockchain() == Blockchain.BitcoinTestNet || card.getBlockchain() == Blockchain.Bitcoin) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin", "bitcoin"); + taskRate.execute(rate); + + }else if (card.getBlockchain() == Blockchain.BitcoinCashTestNet || card.getBlockchain() == Blockchain.BitcoinCash) { + SharedData data = new SharedData(SharedData.COUNT_REQUEST); + + for (int i = 0; i < data.allRequest; ++i) { + String nodeAddress = engine.GetNextNode(card); + int nodePort = engine.GetNextNodePort(card); + + RequestWalletInfoTask connectTaskEx = new RequestWalletInfoTask(nodeAddress, nodePort, data); + connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Electrum_Request.CheckBalance(card.getWallet())); + } + + String nodeAddress = engine.GetNode(card); + int nodePort = engine.GetNodePort(card); + + RequestWalletInfoTask task = new RequestWalletInfoTask(nodeAddress, nodePort); + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(true); //TODO: tmp + + requestTasks.add(task); + task.execute(Electrum_Request.ListUnspent(card.getWallet()), Electrum_Request.ListHistory(card.getWallet())); + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "bitcoin-cash", "bitcoin-cash"); + taskRate.execute(rate); + + } + else if (card.getBlockchain() == Blockchain.Token) { + ETHRequestTask updateETH = new ETHRequestTask(card.getBlockchain()); + Infura_Request reqETH = Infura_Request.GetTokenBalance(card.getWallet(), engine.GetContractAddress(card), engine.GetTokenDecimals(card)); + reqETH.setID(67); + reqETH.setBlockchain(card.getBlockchain()); + + + Infura_Request reqBalance = Infura_Request.GetBalance(card.getWallet()); + reqBalance.setID(67); + reqBalance.setBlockchain(card.getBlockchain()); + + + RateInfoTask taskRate = new RateInfoTask(); + ExchangeRequest rate = ExchangeRequest.GetRate(card.getWallet(), "basic-attention-token", "ethereum"); + taskRate.execute(rate); + + Infura_Request reqNonce = Infura_Request.GetOutTransactionCount(card.getWallet()); + reqNonce.setID(67); + reqNonce.setBlockchain(card.getBlockchain()); + + updateETH.execute(reqETH, reqNonce, reqBalance); + } + } + if (getActivity().getClass() == MainActivity.class) { + ((MainActivity) getActivity()).showCleanButton(); + } + } + }); + } + } + + @Override + public void onViewCard(Bundle cardInfo) { + + if( mSwipeRefreshLayout!=null ) mSwipeRefreshLayout.setRefreshing(false); //TODO: tmp + + String UID = cardInfo.getString("UID"); + Tangem_Card card = new Tangem_Card(UID); + card.LoadFromBundle(cardInfo.getBundle("Card")); + + + Intent intent; + + if (card.getStatus() == Tangem_Card.Status.Empty) { + intent = new Intent(getActivity(), EmptyWalletActivity.class); + } else if (card.getStatus() == Tangem_Card.Status.Loaded) { + intent = new Intent(getActivity(), LoadedWalletActivity.class); + } else if (card.getStatus() == Tangem_Card.Status.NotPersonalized || card.getStatus() == Tangem_Card.Status.Purged) { + return; + } else { + intent = new Intent(getActivity(), LoadedWalletActivity.class); + } + + intent.putExtras(cardInfo); + startActivityForResult(intent, REQUEST_CODE_SHOW_CARD_ACTIVITY); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(logTag, "ActivityResult: requestCode = " + requestCode + ", resultCode = " + resultCode); + // если пришло ОК + if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_SHOW_CARD_ACTIVITY) { + if (data != null && data.getExtras().containsKey("UID")) { + final Tangem_Card card = new Tangem_Card(data.getStringExtra("UID")); + card.LoadFromBundle(data.getBundleExtra("Card")); + if (data.getStringExtra("modification").equals("delete")) { + mCardListAdapter.removeCard(card); + for (int i = 0; i < slCardUIDs.size(); i++) { + if (slCardUIDs.get(i).equals(card.getUID())) { + slCardUIDs.remove(i); + break; + } + } + if (mCardListAdapter.getItemCount() == 0 && getActivity().getClass() == MainActivity.class) { + ((MainActivity) getActivity()).hideCleanButton(); + } + } else if (data.getStringExtra("modification").equals("update")) { + mCardListAdapter.updateCard(card); + } else if (data.getStringExtra("modification").equals("updateAndViewCard")) { + mCardListAdapter.updateCard(card); + onViewCard(data.getExtras()); + } + + } + } else if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_ENTER_PIN_ACTIVITY) { + if (lastTag != null) onTagDiscovered(lastTag); + } + } + + @Override + public void doClean() { + mCardListAdapter.clearCards(); + slCardUIDs.clear(); + for (RequestWalletInfoTask rt : requestTasks) { + rt.cancel(true); + } + lastRead_UID = ""; + } + + public void doEnterPIN() { + Intent intent = new Intent(getContext(), RequestPINActivity.class); + intent.putExtra("mode", RequestPINActivity.Mode.RequestPIN.toString()); + startActivityForResult(intent, REQUEST_CODE_ENTER_PIN_ACTIVITY); + } + + private static String lastRead_UID = ""; + private static ArrayList lastRead_UnsuccessfullPINs = new ArrayList<>(); + private static Tangem_Card.EncryptionMode lastRead_Encryption = null; + + private class ReadCardInfoTask extends Thread { + + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + ReadCardInfoTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + try { + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + CardProtocol protocol = new CardProtocol(getContext(), mIsoDep, mNotifications); + mNotifications.OnReadStart(protocol); + try { + mNotifications.OnReadProgress(protocol, 5); + + byte[] UID = mIsoDep.getTag().getId(); + String sUID = Util.byteArrayToHexString(UID); + if (!lastRead_UID.equals(sUID)) { + lastRead_UID = sUID; + lastRead_UnsuccessfullPINs.clear(); + lastRead_Encryption = null; + } + + Log.i("ReadCardInfoTask", "[-- Start read card info --]"); + + if (isCancelled) return; + + protocol.setPIN(PINStorage.getDefaultPIN()); + protocol.clearReadResult(); + + if (lastRead_Encryption == null) { + Log.i("ReadCardInfoTask", "Try get supported encryption mode"); + protocol.run_GetSupportedEncryption(); + } else { + Log.i("ReadCardInfoTask", "Use already defined encryption mode: " + lastRead_Encryption.name()); + protocol.getCard().encryptionMode = lastRead_Encryption; + } + + if (protocol.haveReadResult()) { + //already have read result (obtained while get supported encryption), only read issuer data and define offline balance + protocol.parseReadResult(); + protocol.run_ReadOrWriteIssuerDataAndDefineOfflineBalance(); + mNotifications.OnReadProgress(protocol, 60); + PINStorage.setLastUsedPIN(protocol.getCard().getPIN()); + } else { + //don't have read result - may be don't get supported encryption on this try, need encryption or need another PIN + if (lastRead_Encryption == null) { + // we try get supported encryption on this time + lastRead_Encryption = protocol.getCard().encryptionMode; + if (protocol.getCard().encryptionMode == Tangem_Card.EncryptionMode.None) { + // default pin not accepted + lastRead_UnsuccessfullPINs.add(PINStorage.getDefaultPIN()); + } + } + + boolean pinFound = false; + for (String PIN : PINStorage.getPINs()) { + Log.e("ReadCardInfoTask", "PIN: " + PIN); + + boolean skipPin = false; + for (int i = 0; i < lastRead_UnsuccessfullPINs.size(); i++) { + if (lastRead_UnsuccessfullPINs.get(i).equals(PIN)) { + skipPin = true; + break; + } + } + + if (skipPin) { + Log.e("ReadCardInfoTask", "Skip PIN - already checked before"); + continue; + } + + try { + protocol.setPIN(PIN); + if (protocol.getCard().encryptionMode != Tangem_Card.EncryptionMode.None) { + protocol.CreateProtocolKey(); + } + protocol.run_Read(); + mNotifications.OnReadProgress(protocol, 60); + PINStorage.setLastUsedPIN(PIN); + pinFound = true; + protocol.getCard().setPIN(PIN); + break; + } catch (CardProtocol.TangemException_InvalidPIN e) { + Log.e(logTag, e.getMessage()); + lastRead_UnsuccessfullPINs.add(PIN); + } + } + if (!pinFound) { + throw new CardProtocol.TangemException_InvalidPIN("No valid PIN found!"); + } + } + + protocol.run_CheckPIN2isDefault(); + + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("ReadCardInfoTask", "[-- Finish read card info --]"); + mNotifications.OnReadFinish(protocol); + } + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + readCardInfoTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + addCard(cardProtocol.getCard()); + } + }); + } else { + // remove last UIDs because of error and no card read + progressBar.post(new Runnable() { + @Override + public void run() { + Toast.makeText(getContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + unsuccessReadCount++; + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + slCardUIDs.remove(cardProtocol.getCard().getUID()); + if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + doEnterPIN(); + } else if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getActivity().getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + lastTag = null; + } + } + }); + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + readCardInfoTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(getActivity(), msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(getActivity(), timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(getActivity()); + } + + private class RequestWalletInfoTask extends Electrum_Task { + public RequestWalletInfoTask(String host, int port) { + super(host, port); + } + + + public RequestWalletInfoTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onCancelled() { + super.onCancelled(); + requestTasks.remove(this); + if ( mSwipeRefreshLayout!=null && requestTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); //TODO: tmp + + } + + void FinishWithError(String wallet, String message) { + mCardListAdapter.UpdateWalletError(wallet, "Cannot obtain data from blockchain"); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + requestTasks.remove(this); + Log.i("RequestWalletInfoTask", "onPostExecute[" + String.valueOf(requests.size()) + "]"); + + CoinEngine engine = CoinEngineFactory.Create(Blockchain.Bitcoin); + + for (Electrum_Request request : requests) { + try { + + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_GetBalance)) { + try { + Long conf = request.getResult().getLong("confirmed"); + Long unconf = request.getResult().getLong("unconfirmed"); + if (sharedCounter != null) { + int counter = sharedCounter.requestCounter.incrementAndGet(); + if (counter != 1) { + continue; + } + } + String mWalletAddress = request.getParams().getString(0); + mCardListAdapter.UpdateWalletBalance(mWalletAddress, conf, unconf, getValidationNodeDescription()); + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter == sharedCounter.allRequest) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } + } else if (request.isMethod(Electrum_Request.METHOD_ListUnspent)) { + try { + String mWalletAddress = request.getParams().getString(0); + mCardListAdapter.UpdateWalletUnspent(mWalletAddress, request.getResultArray()); + + JSONArray unspentList = request.getResultArray(); + + for (int i = 0; i < unspentList.length(); i++) { + JSONObject jsUnspent = unspentList.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + RequestWalletInfoTask task = new RequestWalletInfoTask(request.Host, request.Port); + requestTasks.add(task); + task.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHistory)) { + try { + String mWalletAddress = request.getParams().getString(0); + mCardListAdapter.UpdateWalletHistory(mWalletAddress, request.getResultArray()); + + JSONArray historyList = request.getResultArray(); + + for (int i = 0; i < historyList.length(); i++) { + JSONObject jsUnspent = historyList.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + RequestWalletInfoTask task = new RequestWalletInfoTask(request.Host, request.Port); + requestTasks.add(task); + + task.execute(Electrum_Request.GetHeader(mWalletAddress, String.valueOf(height)), + Electrum_Request.GetTransaction(mWalletAddress, hash)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetHeader)) { + try { + String mWalletAddress = request.WalletAddress; + + mCardListAdapter.UpdateWalletHeader(mWalletAddress, request.getResult()); + + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } else if (request.isMethod(Electrum_Request.METHOD_GetTransaction)) { + try { + Log.e("MainActivityFragment_TX", request.TxHash); + String mWalletAddress = request.WalletAddress; + String tx = request.TxHash; + String raw = request.getResultString(); + Log.e("MainActivityFragment_R", raw); + mCardListAdapter.UpdateTransaction(mWalletAddress, tx, raw); + + } catch (JSONException e) { + e.printStackTrace(); + mCardListAdapter.UpdateWalletError(request.WalletAddress, "Cannot obtain data from blockchain"); + FinishWithError(request.WalletAddress, e.toString()); + engine.SwitchNode(null); + } + } + } else { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + FinishWithError(request.WalletAddress, request.error); + engine.SwitchNode(null); + } + } else { + FinishWithError(request.WalletAddress, request.error); + engine.SwitchNode(null); + } + + } + } catch (JSONException e) { + if (sharedCounter != null) { + int errCounter = sharedCounter.errorRequest.incrementAndGet(); + if (errCounter >= sharedCounter.allRequest) { + e.printStackTrace(); + mCardListAdapter.UpdateWalletError(request.WalletAddress, "Cannot obtain data from blockchain"); + engine.SwitchNode(null); + } + } else { + e.printStackTrace(); + mCardListAdapter.UpdateWalletError(request.WalletAddress, "Cannot obtain data from blockchain"); + engine.SwitchNode(null); + } + + } + } + + if ( mSwipeRefreshLayout!=null && requestTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); //TODO: tmp + } + + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain) { + super(blockchain); + } + + void FinishWithError(String wallet, String message) { + mCardListAdapter.UpdateWalletError(wallet, "Cannot obtain data from blockchain"); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + if (request.error == null) { + + if (request.isMethod(Infura_Request.METHOD_ETH_GetBalance)) { + try { + String mWalletAddress = request.getParams().getString(0); + + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + BigInteger d = l.divide(new BigInteger("1000000000000000000", 10)); + Long balance = d.longValue(); + if (request.getBlockchain() != Blockchain.Token) + mCardListAdapter.UpdateWalletBalance(mWalletAddress, balance, l.toString(10), getValidationNodeDescription()); + mCardListAdapter.UpdateWalletBalanceOnlyAlter(mWalletAddress, l.toString(10)); + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_Call)) { + try { + String mWalletAddress = request.WalletAddress; + + String balanceCap = request.getResultString(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + Long balance = l.longValue(); + if (l.compareTo(BigInteger.ZERO) == 0) { + mCardListAdapter.UpdateWalletBlockchain(mWalletAddress, Blockchain.Ethereum); + mCardListAdapter.AddWalletBlockchainNameToken(mWalletAddress); + Tangem_Card card = mCardListAdapter.getCardByWallet(request.WalletAddress); + if (card != null) { + refreshCard(card); + } + return; + } + mCardListAdapter.UpdateWalletBalance(mWalletAddress, balance, l.toString(10), getValidationNodeDescription()); + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } catch (Exception e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } else if (request.isMethod(Infura_Request.METHOD_ETH_GetOutTransactionCount)) { + try { + String mWalletAddress = request.getParams().getString(0); + + String nonce = request.getResultString(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + + mCardListAdapter.UpdateWalletCoutConfirmTx(mWalletAddress, count); + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } + } else { + FinishWithError(request.WalletAddress, request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(request.WalletAddress, e.toString()); + } + } + } + + } + + private class RateInfoTask extends ExchangeTask { + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (ExchangeRequest request : requests) { + if (request.error == null) { + try { + + JSONArray arr = request.getAnswerList(); + for (int i = 0; i < arr.length(); ++i) { + JSONObject obj = arr.getJSONObject(i); + String currency = obj.getString("id"); + + boolean stop = false; + boolean stopAlter = false; + if (currency.equals(request.currency)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCardListAdapter.UpdateRate(request.WalletAddress, rate); + stop = true; + } + + if (currency.equals(request.currencyAlter)) { + String usd = obj.getString("price_usd"); + + Float rate = Float.valueOf(usd); + mCardListAdapter.UpdateRateAlter(request.WalletAddress, rate); + stopAlter = true; + } + + if (stop && stopAlter) { + break; + } + } + + + //mCardListAdapter.UpdateWalletBalance(mWalletAddress, balance, l.toString(10)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + } + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/Manufacturer.java b/app/src/main/java/com/tangem/wallet/Manufacturer.java new file mode 100644 index 0000000000..c904f9b308 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Manufacturer.java @@ -0,0 +1,126 @@ +package com.tangem.wallet; + +import android.util.Log; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.jce.ECNamedCurveTable; +import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.util.Arrays; + +/** + * Created by dvol on 09.08.2017. + */ + +public enum Manufacturer { +// Unknown("", "Unknown", new byte[]{}), +// SMARTCASH_AG("SMART CASH AG","SMART CASH AG", +// new byte[]{0x04, +// (byte) 0x4F, (byte) 0x53, (byte) 0x90, (byte) 0x2D, (byte) 0x50, (byte) 0xE2, (byte) 0xBB, (byte) 0x16, +// (byte) 0xD3, (byte) 0xDD, (byte) 0xC7, (byte) 0xA2, (byte) 0x03, (byte) 0x97, (byte) 0x28, (byte) 0x5E, +// (byte) 0x94, (byte) 0x21, (byte) 0x53, (byte) 0x69, (byte) 0x59, (byte) 0x8C, (byte) 0xE4, (byte) 0xDD, +// (byte) 0x62, (byte) 0x42, (byte) 0xDD, (byte) 0xB4, (byte) 0x5B, (byte) 0x96, (byte) 0xA1, (byte) 0x03, +// (byte) 0x1A, (byte) 0xF5, (byte) 0xC9, (byte) 0x73, (byte) 0x94, (byte) 0xC6, (byte) 0xF9, (byte) 0xC8, +// (byte) 0xD7, (byte) 0x6F, (byte) 0x38, (byte) 0xF9, (byte) 0x65, (byte) 0xCB, (byte) 0xA8, (byte) 0xAE, +// (byte) 0x85, (byte) 0xAF, (byte) 0xF7, (byte) 0x68, (byte) 0x55, (byte) 0xDC, (byte) 0xAA, (byte) 0x08, +// (byte) 0xF3, (byte) 0xCD, (byte) 0x15, (byte) 0x43, (byte) 0x04, (byte) 0x19, (byte) 0xF4, (byte) 0x49}), +// DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG","SMART CASH AG (DEVELOPERS)", +// new byte[]{0x04, +// (byte) 0xBA, (byte) 0xB8, (byte) 0x6D, (byte) 0x56, (byte) 0x29, (byte) 0x8C, (byte) 0x99, (byte) 0x6F, +// (byte) 0x56, (byte) 0x4A, (byte) 0x84, (byte) 0xFC, (byte) 0x88, (byte) 0xE2, (byte) 0x8A, (byte) 0xED, +// (byte) 0x38, (byte) 0x18, (byte) 0x4B, (byte) 0x12, (byte) 0xF0, (byte) 0x7E, (byte) 0x51, (byte) 0x91, +// (byte) 0x13, (byte) 0xBE, (byte) 0xF4, (byte) 0x8C, (byte) 0x76, (byte) 0xF3, (byte) 0xDF, (byte) 0x3A, +// +// (byte) 0xDC, (byte) 0x30, (byte) 0x35, (byte) 0x99, (byte) 0xB0, (byte) 0x8A, (byte) 0xC0, (byte) 0x5B, +// (byte) 0x55, (byte) 0xEC, (byte) 0x3D, (byte) 0xF9, (byte) 0x8D, (byte) 0x93, (byte) 0x38, (byte) 0x57, +// (byte) 0x3A, (byte) 0x62, (byte) 0x42, (byte) 0xF7, (byte) 0x6F, (byte) 0x5D, (byte) 0x28, (byte) 0xF4, +// (byte) 0xF0, (byte) 0xF3, (byte) 0x64, (byte) 0xE8, (byte) 0x7E, (byte) 0x8F, (byte) 0xCA, (byte) 0x2F}); + + + Unknown("", "Unknown"), + SMARTCASH_AG("SMART CASH AG", "SMART CASH AG"), + DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG", "SMART CASH AG (DEVELOPERS)"), + SMARTCASH("SMART CASH", "SMART CASH"); + + private String ID; + private String officialName; +// private byte[] publicKey; + + // Manufacturer(String id, String officialName, byte[] publicKey) { + Manufacturer(String id, String officialName) { + this.ID = id; + this.officialName = officialName; +// this.publicKey = publicKey; + } + + public String getOfficialName() { + return officialName; + } + +// public boolean VerifySignature(byte[] Challenge, byte[] Salt, byte[] Signature) { +// +// if (publicKey == null || Challenge == null || Salt == null || Signature == null) { +// Log.e(getOfficialName(), "Not all data read, can't check signature!"); +// return false; +// } +// try { +// java.security.Signature signature = java.security.Signature.getInstance("SHA256withECDSA"); +// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); +// KeyFactory factory = KeyFactory.getInstance("EC", "SC"); +// +// ECPoint p1 = spec.getCurve().decodePoint(publicKey); +// +// ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec); +// +// PublicKey publicKey = factory.generatePublic(keySpec); +// signature.initVerify(publicKey); +// signature.update(Challenge); +// signature.update(Salt); +// +// ASN1EncodableVector v = new ASN1EncodableVector(); +// int size = Signature.length / 2; +// v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(Signature, 0, size)))); +// v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(Signature, size, size * 2)))); +// byte[] sigDer = new DERSequence(v).getEncoded(); +// +// if (signature.verify(sigDer)) { +// Log.i(getOfficialName(), "Signature verification OK"); +// return true; +// } else { +// Log.e(getOfficialName(), "Signature verification failed"); +// } +// } +// catch (Exception e) +// { +// e.printStackTrace(); +// } +// return false; +// } +// +// public static Manufacturer FindManufacturer(String ID, byte[] Challenge, byte[] Salt, byte[] Signature) { +// Manufacturer[] manufacturers = Manufacturer.values(); +// for (int i = 1; i < manufacturers.length; i++) { +// if ( manufacturers[i].ID.equals(ID) && manufacturers[i].VerifySignature(Challenge, Salt, Signature)) { +// return manufacturers[i]; +// } +// } +// return Manufacturer.Unknown; +// } + + public static Manufacturer FindManufacturer(String ID) { + Manufacturer[] manufacturers = Manufacturer.values(); + for (int i = 1; i < manufacturers.length; i++) { + if (manufacturers[i].ID.equals(ID)) { + return manufacturers[i]; + } + } + return Manufacturer.Unknown; + } +} diff --git a/app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java b/app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java new file mode 100644 index 0000000000..d5217286e0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/NoExtendedLengthSupportDialog.java @@ -0,0 +1,34 @@ +package com.tangem.wallet; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; + +public class NoExtendedLengthSupportDialog extends DialogFragment { + + public static boolean allreadyShowed=false; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Warning") + .setMessage("The NFC adapter of the device does not support extended length APDU, it's possible that some functions will not work!") + .setPositiveButton("Got it", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + NoExtendedLengthSupportDialog.allreadyShowed=true; + } + } + ) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + } +} diff --git a/app/src/main/java/com/tangem/wallet/PINStorage.java b/app/src/main/java/com/tangem/wallet/PINStorage.java new file mode 100644 index 0000000000..b1e376d3aa --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PINStorage.java @@ -0,0 +1,206 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; + +import com.tangem.cardReader.CardProtocol; + +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.Cipher; + +/** + * Created by dvol on 12.09.2017. + * Global PIN Storage + */ + +class PINStorage { + private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2; + private static SharedPreferences sharedPreferences=null; + + static void Init(Context context) { + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + mSavedPIN = sharedPreferences.getString("SavedPIN", null); + mUserPIN = null; + mLastUsedPIN = null; + mEncryptedPIN = null; + mPIN2 = null; + } + + + static List getPINs() { + ArrayList result = new ArrayList<>(); + if (mLastUsedPIN != null) result.add(mLastUsedPIN); + if (mEncryptedPIN != null && !result.contains(mEncryptedPIN)) result.add(mEncryptedPIN); + if (mUserPIN != null && !result.contains(mUserPIN)) result.add(mUserPIN); + if (mSavedPIN != null && !result.contains(mSavedPIN)) result.add(mSavedPIN); + if (!result.contains(CardProtocol.DefaultPIN)) result.add(CardProtocol.DefaultPIN); + return result; + } + + static void setLastUsedPIN(String PIN) { + mLastUsedPIN = PIN; + } + + static void setUserPIN(String PIN) { + mUserPIN = PIN; + } + + static void setPIN2(String PIN) { + mPIN2 = PIN; + } + + static void savePIN(String PIN) { + mSavedPIN = PIN; + if (mSavedPIN != null && !mSavedPIN.isEmpty()) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("SavedPIN", mSavedPIN); + editor.apply(); + } else { + deletePIN(); + } + } + + static void deletePIN() { + SharedPreferences.Editor editor = sharedPreferences.edit(); + if (mSavedPIN != null && mLastUsedPIN != null && mSavedPIN.equals(mLastUsedPIN)) { + mLastUsedPIN = null; + } + mSavedPIN = null; + editor.remove("SavedPIN"); + editor.apply(); + } + + static void saveEncryptedPIN(Cipher cipher, String PIN) { + try { + byte[] iv = cipher.getIV(); + byte[] bytes = cipher.doFinal(PIN.getBytes()); + String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP); + String sIV = Base64.encodeToString(iv, Base64.NO_WRAP); + +// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("EncryptedPIN", encryptedPIN); + editor.putString("EncryptedIV", sIV); + editor.apply(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + static byte[] loadEncryptedIV() { + String sIV = sharedPreferences.getString("EncryptedIV", ""); +// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV)); + + return Base64.decode(sIV, Base64.NO_WRAP); + } + + static String loadEncryptedPIN(Cipher cipher) { + String encryptedPIN = sharedPreferences.getString("EncryptedPIN", null); + + try { + byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP); + mEncryptedPIN = new String(cipher.doFinal(bytes)); +// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN)); + } catch (Exception e) { + e.printStackTrace(); + mEncryptedPIN = null; + } + return mEncryptedPIN; + } + + static boolean haveEncryptedPIN() { + return sharedPreferences.getString("EncryptedPIN", null) != null; + } + + static void deleteEncryptedPIN() { + if (mEncryptedPIN != null && mLastUsedPIN != null && mEncryptedPIN.equals(mLastUsedPIN)) { + mLastUsedPIN = null; + } + mEncryptedPIN = null; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove("EncryptedPIN"); + editor.remove("EncryptedIV"); + editor.apply(); + } + + static void saveEncryptedPIN2(Cipher cipher, String PIN) { + try { + byte[] iv = cipher.getIV(); + byte[] bytes = cipher.doFinal(PIN.getBytes()); + String encryptedPIN = Base64.encodeToString(bytes, Base64.NO_WRAP); + String sIV = Base64.encodeToString(iv, Base64.NO_WRAP); + +// Log.d("PINStorage", String.format("saveEncryptedPIN: %s, encrypted: %s, iv: %s",PIN,encryptedPIN,sIV)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("EncryptedPIN2", encryptedPIN); + editor.putString("EncryptedIV2", sIV); + editor.apply(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + static byte[] loadEncryptedIV2() { + String sIV = sharedPreferences.getString("EncryptedIV2", ""); +// Log.d("PINStorage", String.format("loadEncryptedIV: %s",sIV)); + + return Base64.decode(sIV, Base64.NO_WRAP); + } + + static String loadEncryptedPIN2(Cipher cipher) { + String encryptedPIN = sharedPreferences.getString("EncryptedPIN2", null); + + try { + byte[] bytes = Base64.decode(encryptedPIN, Base64.NO_WRAP); + mPIN2 = new String(cipher.doFinal(bytes)); +// Log.d("PINStorage", String.format("loadEncryptedPIN: %s (encrypted: %s)",mEncryptedPIN,encryptedPIN)); + } catch (Exception e) { + e.printStackTrace(); + mPIN2 = null; + } + return mPIN2; + } + + static boolean haveEncryptedPIN2() { + return sharedPreferences.getString("EncryptedPIN2", null) != null; + } + + static void deleteEncryptedPIN2() { + mPIN2 = null; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove("EncryptedPIN2"); + editor.remove("EncryptedIV2"); + editor.apply(); + } + + public static String getPIN2() { + return mPIN2; + } + + public static boolean isDefaultPIN(String pin) { + return (pin != null) && (CardProtocol.DefaultPIN.equals(pin)); + } + + public static boolean isDefaultPIN2(String pin2) { + return (pin2 != null) && (CardProtocol.DefaultPIN2.equals(pin2)); + } + + public static String getDefaultPIN() { + return CardProtocol.DefaultPIN; + } + + public static String getDefaultPIN2() { + return CardProtocol.DefaultPIN2; + } + + public static boolean needInit() { + return sharedPreferences==null; + } +} diff --git a/app/src/main/java/com/tangem/wallet/PhoneUtility.java b/app/src/main/java/com/tangem/wallet/PhoneUtility.java new file mode 100644 index 0000000000..617286e30c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PhoneUtility.java @@ -0,0 +1,35 @@ +package com.tangem.wallet; + +import android.os.Build; + +/** + * Created by Ilia on 20.04.2018. + */ + +public class PhoneUtility { + public static String GetPhoneName() + { + return DeviceName.getDeviceName(); + + } + + public static String getDeviceInfo() { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append("----------------------------------------\n"); + stringBuilder.append("MODEL: ").append(Build.MODEL).append("\n"); + stringBuilder.append("ID: ").append(Build.ID).append("\n"); + stringBuilder.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n"); + stringBuilder.append("Brand: ").append(Build.BRAND).append("\n"); + stringBuilder.append("Hardware: ").append(Build.HARDWARE).append("\n"); + stringBuilder.append("Version: ").append(Build.VERSION.RELEASE).append(", ").append(Build.VERSION.INCREMENTAL).append("\n"); + stringBuilder.append("OS: ").append(Build.VERSION.BASE_OS).append("\n"); + stringBuilder.append("SDK: ").append(Build.VERSION.SDK_INT).append("\n"); + stringBuilder.append("BOARD: ").append(Build.BOARD).append("\n"); + stringBuilder.append("FINGERPRINT: ").append(Build.FINGERPRINT).append("\n"); + stringBuilder.append("----------------------------------------\n"); + + return stringBuilder.toString(); + + } +} diff --git a/app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java b/app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java new file mode 100644 index 0000000000..886f711168 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PreparePaymentActivity.java @@ -0,0 +1,237 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.Html; +import android.text.Spanned; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tangem.cardReader.NfcManager; + +import java.io.IOException; + + +public class PreparePaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback { + + private static final int REQUEST_CODE_SCAN_QR = 1; + private static final int REQUEST_CODE_SEND_PAYMENT = 2; + Button btnVerify; + EditText etWallet; + EditText etAmount; + TextView tvCurrency; + TextView tvCardId, tvBalance, tvBalanceEquivalent, tvAmountEquivalent; + ImageView ivCamera; + boolean use_mCurrency; + Tangem_Card mCard; + private NfcManager mNfcManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_prepare_payment); + + MainActivity.commonInit(getApplicationContext()); + + mNfcManager = new NfcManager(this, this); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + btnVerify = (Button) findViewById(R.id.btnVerify); + etWallet = (EditText) findViewById(R.id.etWallet); + etAmount = (EditText) findViewById(R.id.etAmount); + ivCamera = (ImageView) findViewById(R.id.ivCamera); + tvCurrency = (TextView) findViewById(R.id.tvCurrency); + tvCardId = (TextView) findViewById(R.id.tvCardID); + tvBalance = (TextView) findViewById(R.id.tvBalance); + tvBalanceEquivalent = (TextView) findViewById(R.id.tvBalanceEquivalent); + tvAmountEquivalent = (TextView) findViewById(R.id.tvAmountEquivalent); + + tvCardId.setText(mCard.getCIDDescription()); + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + + if (mCard.getBlockchain() == Blockchain.Token) { + Spanned html = Html.fromHtml(engine.GetBalanceWithAlter(mCard)); + tvBalance.setText(html); + } else { + tvBalance.setText(engine.GetBalanceWithAlter(mCard)); + } + + tvBalanceEquivalent.setText(engine.GetBalanceEquivalent(mCard)); + + if(etAmount != null && mCard.getRemainingSignatures()<2) + { + etAmount.setEnabled(false); + } + + if( !mCard.getAmountEquivalentDescriptionAvailable()) + { + tvBalanceEquivalent.setError("Service unavailable"); + }else{ + tvBalanceEquivalent.setError(null); + } + + etAmount.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + tvAmountEquivalent.setText(engine.GetAmountEqualentDescriptor(mCard, etAmount.getText().toString())); + if (!mCard.getAmountEquivalentDescriptionAvailable()) { + tvAmountEquivalent.setError("Service unavailable"); + }else{ + tvAmountEquivalent.setError(null); + } + } catch (Exception e) { + e.printStackTrace(); + tvAmountEquivalent.setText(""); + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + if(mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet) + { + tvCurrency.setText(engine.GetBalanceCurrency(mCard)); + use_mCurrency=false; + etAmount.setText(engine.GetBalanceValue(mCard)); + } + else if(mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet) + { + Double balance = engine.GetBalanceLong(mCard) / (mCard.getBlockchain().getMultiplier() / 1000.0); + tvCurrency.setText("m" + mCard.getBlockchain().getCurrency()); + use_mCurrency=true; + String output = FormatUtil.DoubleToString(balance); + etAmount.setText(output); + } + else if(mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet) + { + Double balance = engine.GetBalanceLong(mCard) / (mCard.getBlockchain().getMultiplier() / 1000.0); + tvCurrency.setText("m" + mCard.getBlockchain().getCurrency()); + use_mCurrency=true; + String output = FormatUtil.DoubleToString(balance); + etAmount.setText(output); + } + else + { + tvCurrency.setText(engine.GetBalanceCurrency(mCard)); + use_mCurrency=false; + etAmount.setText(engine.GetBalanceValue(mCard)); + } + + btnVerify.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String strAmount; + strAmount=etAmount.getText().toString(); + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + try { + if(!engine.CheckAmount(mCard, etAmount.getText().toString())) + { + etAmount.setError("Not enough funds on your card"); + } + } + catch(Exception e) + { + etAmount.setError("Unknown amount format"); + return; + } + + boolean checkAddress = engine.ValdateAddress(etWallet.getText().toString(), mCard); + if(!checkAddress) + { + etWallet.setError("Incorrect destination wallet address"); + return; + } + + if(etWallet.getText().toString().equals(mCard.getWallet())) + { + etWallet.setError("Destination wallet address equal source address"); + return; + } + + Intent intent = new Intent(getBaseContext(), ConfirmPaymentActivity.class); + intent.putExtra("UID", mCard.getUID()); + intent.putExtra("Card", mCard.getAsBundle()); + intent.putExtra("Wallet", etWallet.getText().toString()); + intent.putExtra("Amount", strAmount); + startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT); + } + }); + + ivCamera.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getBaseContext(), QRScanActivity.class); + startActivityForResult(intent, REQUEST_CODE_SCAN_QR); + } + }); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.getExtras().containsKey("QRCode")) { + String code = data.getStringExtra("QRCode"); + if(code.contains("bitcoin:")) + { + String tmp[] = code.split("bitcoin:"); + code = tmp[1]; + } + etWallet.setText(code); + }else if (requestCode == REQUEST_CODE_SEND_PAYMENT ) { + + setResult(resultCode,data); + finish(); + } + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(),"Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/PurgeActivity.java b/app/src/main/java/com/tangem/wallet/PurgeActivity.java new file mode 100644 index 0000000000..1f53a0a54c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/PurgeActivity.java @@ -0,0 +1,333 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class PurgeActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; + + private Tangem_Card mCard; + private TextView tvCardID; + private NfcManager mNfcManager; + private static final String logTag = "Purge"; + private ProgressBar progressBar; + private PurgeTask purgeTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_purge); + + MainActivity.commonInit(getApplicationContext()); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + tvCardID = (TextView) findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + mNfcManager = new NfcManager(this, this); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + Log.v(logTag, "UID: " + sUID); + + if (sUID.equals(mCard.getUID())) { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); + purgeTask = new PurgeTask(isoDep, this); + + purgeTask.start(); + } else { + Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); + mNfcManager.IgnoreTag(isoDep.getTag()); + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (purgeTask != null) { + purgeTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (purgeTask != null) { + purgeTask.cancel(true); + } + super.onStop(); + } + +// @Override +// public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) { +// return mNfcManager.onCreateDialog(id, builder, li); +// } + + private class PurgeTask extends Thread { + + + private String txOutAddress; + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + public PurgeTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); + + mNotifications.OnReadStart(protocol); + try { + + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + + mNotifications.OnReadProgress(protocol, 5); + + Log.i("PurgeTask", "[-- Start purge --]"); + + if (isCancelled) return; + + if (mCard.getPauseBeforePIN2() > 0) { + mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); + } + +// try { + protocol.run_PurgeWallet(PINStorage.getPIN2()); +// } finally { +// mNotifications.OnReadWait(0); +// } + + mNotifications.OnReadProgress(protocol, 50); + + protocol.run_Read(); + + mNotifications.OnReadProgress(protocol, 100); + + if (isCancelled) return; + + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("PurgeTask", "[-- Finish purge --]"); + mNotifications.OnReadFinish(protocol); + } + } + + public void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + purgeTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } else { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + intent.putExtra("message", "Cannot erase wallet. Make sure you enter correct PIN2!"); + setResult(RESULT_INVALID_PIN, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + return; + } else { + progressBar.post(new Runnable() { + @Override + public void run() { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + } + } + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + purgeTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/QRScanActivity.java b/app/src/main/java/com/tangem/wallet/QRScanActivity.java new file mode 100644 index 0000000000..bd555763b4 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/QRScanActivity.java @@ -0,0 +1,90 @@ +package com.tangem.wallet; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.google.zxing.Result; + +import me.dm7.barcodescanner.zxing.ZXingScannerView; + +public class QRScanActivity extends AppCompatActivity implements ZXingScannerView.ResultHandler{ + + private ZXingScannerView mScannerView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //setContentView(R.layout.activity_qrscan); + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + Log.e("QRScanActivity","User hasn't granted permission to use camera"); + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA}, 1); + }else { + runScanner(); + } + } + + void runScanner() + { + mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view + setContentView(mScannerView); + mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results. + mScannerView.startCamera(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + switch (requestCode) { + case 1: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + Log.i("QRScanActivity","permission was granted"); + // permission was granted, yay! Do the + // contacts-related task you need to do. + runScanner(); + + } else { + Log.e("QRScanActivity","permission denied"); + setResult(Activity.RESULT_CANCELED); + finish(); + + // permission denied, boo! Disable the + // functionality that depends on this permission. + } + return; + } + + // other 'case' lines to check for other + // permissions this app might request + } + } + + + @Override + public void handleResult(Result result) { + Intent data=new Intent(); + data.putExtra("QRCode", result.getText()); + setResult(Activity.RESULT_OK, data); + finish(); + } + + @Override + protected void onPause() { + super.onPause(); + if( mScannerView!=null ) mScannerView.stopCamera(); // Stop camera on pause + } + + @Override + protected void onResume() { + super.onResume(); + if( mScannerView!=null ) mScannerView.startCamera(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/RLP.java b/app/src/main/java/com/tangem/wallet/RLP.java new file mode 100644 index 0000000000..b3d85d7687 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RLP.java @@ -0,0 +1,228 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 07.01.2018. + */ + +import java.util.Arrays; + +import static com.tangem.wallet.ByteUtil.isNullOrZeroArray; +import static com.tangem.wallet.ByteUtil.isSingleZero; + +public class RLP { + public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]); + + /** + * Allow for content up to size of 2^64 bytes * + */ + private static final double MAX_ITEM_LENGTH = Math.pow(256, 8); + + /** + * Reason for threshold according to Vitalik Buterin: + * - 56 bytes maximizes the benefit of both options + * - if we went with 60 then we would have only had 4 slots for long strings + * so RLP would not have been able to store objects above 4gb + * - if we went with 48 then RLP would be fine for 2^128 space, but that's way too much + * - so 56 and 2^64 space seems like the right place to put the cutoff + * - also, that's where Bitcoin's varint does the cutof + */ + private static final int SIZE_THRESHOLD = 56; + + /** RLP encoding rules are defined as follows: */ + + /* + * For a single byte whose value is in the [0x00, 0x7f] range, that byte is + * its own RLP encoding. + */ + + /** + * [0x80] + * If a string is 0-55 bytes long, the RLP encoding consists of a single + * byte with value 0x80 plus the length of the string followed by the + * string. The range of the first byte is thus [0x80, 0xb7]. + */ + private static final int OFFSET_SHORT_ITEM = 0x80; + + /** + * [0xb7] + * If a string is more than 55 bytes long, the RLP encoding consists of a + * single byte with value 0xb7 plus the length of the length of the string + * in binary form, followed by the length of the string, followed by the + * string. For example, a length-1024 string would be encoded as + * \xb9\x04\x00 followed by the string. The range of the first byte is thus + * [0xb8, 0xbf]. + */ + private static final int OFFSET_LONG_ITEM = 0xb7; + + /** + * [0xc0] + * If the total payload of a list (i.e. the combined length of all its + * items) is 0-55 bytes long, the RLP encoding consists of a single byte + * with value 0xc0 plus the length of the list followed by the concatenation + * of the RLP encodings of the items. The range of the first byte is thus + * [0xc0, 0xf7]. + */ + private static final int OFFSET_SHORT_LIST = 0xc0; + + public static byte[] encodeByte(byte singleByte) { + if ((singleByte & 0xFF) == 0) { + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + } else if ((singleByte & 0xFF) <= 0x7F) { + return new byte[]{singleByte}; + } else { + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte}; + } + } + + public static byte[] encodeShort(short singleShort) { + if ((singleShort & 0xFF) == singleShort) + return encodeByte((byte) singleShort); + else { + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 2), + (byte) (singleShort >> 8 & 0xFF), + (byte) (singleShort >> 0 & 0xFF)}; + } + } + + public static byte[] encodeInt(int singleInt) { + if ((singleInt & 0xFF) == singleInt) + return encodeByte((byte) singleInt); + else if ((singleInt & 0xFFFF) == singleInt) + return encodeShort((short) singleInt); + else if ((singleInt & 0xFFFFFF) == singleInt) + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 3), + (byte) (singleInt >>> 16), + (byte) (singleInt >>> 8), + (byte) singleInt}; + else { + return new byte[]{(byte) (OFFSET_SHORT_ITEM + 4), + (byte) (singleInt >>> 24), + (byte) (singleInt >>> 16), + (byte) (singleInt >>> 8), + (byte) singleInt}; + } + } + + private static final int OFFSET_LONG_LIST = 0xf7; + + public static byte[] encodeElement2(byte[] srcData) { + if (srcData == null) + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + return srcData; + } else if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); + System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); + data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + return data; + } + } + public static byte[] encodeElement(byte[] srcData) { + + if (isNullOrZeroArray(srcData)) + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + else if (isSingleZero(srcData)) + return srcData; + else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + return srcData; + } else if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); + System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); + data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + return data; + } + } + + + public static byte[] encodeList(byte[]... elements) { + + if (elements == null) { + return new byte[]{(byte) OFFSET_SHORT_LIST}; + } + + int totalLength = 0; + for (byte[] element1 : elements) { + totalLength += element1.length; + } + + byte[] data; + int copyPos; + if (totalLength < SIZE_THRESHOLD) { + + data = new byte[1 + totalLength]; + data[0] = (byte) (OFFSET_SHORT_LIST + totalLength); + copyPos = 1; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = totalLength; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + tmpLength = totalLength; + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + data = new byte[1 + lenBytes.length + totalLength]; + data[0] = (byte) (OFFSET_LONG_LIST + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + copyPos = lenBytes.length + 1; + } + for (byte[] element : elements) { + System.arraycopy(element, 0, data, copyPos, element.length); + copyPos += element.length; + } + return data; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/RLPElement.java b/app/src/main/java/com/tangem/wallet/RLPElement.java new file mode 100644 index 0000000000..ae82526c2d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RLPElement.java @@ -0,0 +1,12 @@ +package com.tangem.wallet; + +import java.io.Serializable; + +/** + * Created by Ilia on 07.01.2018. + */ + +public interface RLPElement extends Serializable { + + byte[] getRLPData(); +} diff --git a/app/src/main/java/com/tangem/wallet/RLPList.java b/app/src/main/java/com/tangem/wallet/RLPList.java new file mode 100644 index 0000000000..14164502b3 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RLPList.java @@ -0,0 +1,37 @@ +package com.tangem.wallet; + +import java.util.ArrayList; + +/** + * Created by Ilia on 07.01.2018. + */ + +public class RLPList extends ArrayList 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 + ", "); + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/RequestPINActivity.java b/app/src/main/java/com/tangem/wallet/RequestPINActivity.java new file mode 100644 index 0000000000..b35a4ec71e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/RequestPINActivity.java @@ -0,0 +1,531 @@ +package com.tangem.wallet; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tangem.cardReader.NfcManager; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +public class RequestPINActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener { + + public enum Mode {RequestPIN, RequestPIN2, RequestNewPIN, RequestNewPIN2, ConfirmNewPIN, ConfirmNewPIN2} + + Mode mode; + boolean allowFingerprint = false; + + private NfcManager mNfcManager; + + private TextView tvPIN; + + private StartFingerprintReaderTask mStartFingerprintReaderTask; + + public static final String KEY_ALIAS = "pinKey"; + public static final String KEYSTORE = "AndroidKeyStore"; + + private FingerprintManager fingerprintManager; + private FingerprintHelper fingerprintHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_request_pin); + + MainActivity.commonInit(getApplicationContext()); + + mNfcManager = new NfcManager(this, this); + + tvPIN = findViewById(R.id.pin); + + OnClickListener onButtonNClick = new OnClickListener() { + @Override + public void onClick(View view) { + tvPIN.setText(tvPIN.getText() + (String) ((Button) view).getText()); + } + }; + + Button btn0 = findViewById(R.id.btn0); + btn0.setOnClickListener(onButtonNClick); + Button btn1 = findViewById(R.id.btn1); + btn1.setOnClickListener(onButtonNClick); + Button btn2 = findViewById(R.id.btn2); + btn2.setOnClickListener(onButtonNClick); + Button btn3 = findViewById(R.id.btn3); + btn3.setOnClickListener(onButtonNClick); + Button btn4 = findViewById(R.id.btn4); + btn4.setOnClickListener(onButtonNClick); + Button btn5 = findViewById(R.id.btn5); + btn5.setOnClickListener(onButtonNClick); + Button btn6 = findViewById(R.id.btn6); + btn6.setOnClickListener(onButtonNClick); + Button btn7 = findViewById(R.id.btn7); + btn7.setOnClickListener(onButtonNClick); + Button btn8 = findViewById(R.id.btn8); + btn8.setOnClickListener(onButtonNClick); + Button btn9 = findViewById(R.id.btn9); + btn9.setOnClickListener(onButtonNClick); + Button btnBS = findViewById(R.id.btnBackspace); + btnBS.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + String S = tvPIN.getText().toString(); + if (S.length() > 0) { + tvPIN.setText(S.substring(0, S.length() - 1)); + } + } + }); + + Button btnContinue = findViewById(R.id.btnContinue); + btnContinue.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doContinue(); + } + }); + + mode = Mode.valueOf(getIntent().getStringExtra("mode")); + TextView tvPrompt = findViewById(R.id.pin_prompt); + if (mode == Mode.RequestNewPIN) { + if (PINStorage.haveEncryptedPIN()) { + allowFingerprint = true; + tvPrompt.setText("Enter new PIN or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter new PIN"); + } + } else if (mode == Mode.ConfirmNewPIN) { + tvPrompt.setText("Confirm new PIN"); + } else if (mode == Mode.RequestPIN) { + if (PINStorage.haveEncryptedPIN()) { + allowFingerprint = true; + tvPrompt.setText("Enter PIN or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter PIN"); + } + } else if (mode == Mode.RequestNewPIN2) { + if (PINStorage.haveEncryptedPIN2()) { + allowFingerprint = true; + tvPrompt.setText("Enter new PIN2 or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter new PIN2"); + } + } else if (mode == Mode.ConfirmNewPIN2) { + tvPrompt.setText("Confirm new PIN2"); + } else if (mode == Mode.RequestPIN2) { + String UID = getIntent().getStringExtra("UID"); + Tangem_Card mCard = new Tangem_Card(UID); + mCard.LoadFromBundle(getIntent().getBundleExtra("Card")); + + if (mCard.PIN2 == Tangem_Card.PIN2_Mode.DefaultPIN2 || mCard.PIN2 == Tangem_Card.PIN2_Mode.Unchecked) { + // if we know PIN2 or not try default previously - use it + PINStorage.setPIN2(PINStorage.getDefaultPIN2()); + setResult(Activity.RESULT_OK); + finish(); + return; + } + + if (PINStorage.haveEncryptedPIN2()) { + allowFingerprint = true; + tvPrompt.setText("Enter PIN2 or use fingerprint scanner"); + } else { + tvPrompt.setText("Enter PIN2"); + } + } + ImageView imgFingerprint = findViewById(R.id.imgFingerprint); + if (!allowFingerprint) { + imgFingerprint.setVisibility(View.GONE); + } else { + imgFingerprint.setVisibility(View.VISIBLE); + } + + } + + @Override + protected void onPause() { + super.onPause(); + + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mStartFingerprintReaderTask != null) { + mStartFingerprintReaderTask.cancel(true); + mStartFingerprintReaderTask = null; + } + + mNfcManager.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mStartFingerprintReaderTask != null) { + + mStartFingerprintReaderTask.cancel(true); + mStartFingerprintReaderTask = null; + } + + mNfcManager.onStop(); + } + + @Override + protected void onResume() { + super.onResume(); + mNfcManager.onResume(); + if (allowFingerprint) { + startFingerprintReader(); + } + } + + private void doContinue() { + if (mStartFingerprintReaderTask != null) { + return; + } + + // Reset errors. + tvPIN.setError(null); + + // Store values at the time of the login attempt. + String pin = tvPIN.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (mode == Mode.ConfirmNewPIN) { + if (!pin.equals(getIntent().getStringExtra("newPIN"))) { + tvPIN.setError(getString(R.string.error_pin_confirmation_failed)); + focusView = tvPIN; + cancel = true; + } + } else if (mode == Mode.ConfirmNewPIN2) { + if (!pin.equals(getIntent().getStringExtra("newPIN2"))) { + tvPIN.setError(getString(R.string.error_pin_confirmation_failed)); + focusView = tvPIN; + cancel = true; + } + } else { + if (TextUtils.isEmpty(pin)) { + tvPIN.setError(getString(R.string.error_empty_pin)); + focusView = tvPIN; + cancel = true; + } + } + + if (cancel) { + focusView.requestFocus(); + } else { + if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { + Intent resultData = new Intent(); + resultData.putExtra("newPIN", pin); + if (mode == Mode.ConfirmNewPIN) { + resultData.putExtra("confirmPIN", pin); + } + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) { + Intent resultData = new Intent(); + resultData.putExtra("newPIN2", pin); + if (mode == Mode.ConfirmNewPIN2) { + resultData.putExtra("confirmPIN2", pin); + } + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestPIN) { + PINStorage.setUserPIN(pin); + setResult(Activity.RESULT_OK); + finish(); + } else if (mode == Mode.RequestPIN2) { + PINStorage.setPIN2(pin); + setResult(Activity.RESULT_OK); + finish(); + } + } + + } + + @Override + public void authenticationFailed(String error) { + doLog(error); + } + + @TargetApi(Build.VERSION_CODES.M) + @Override + public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { + doLog("Authentication succeeded!"); + Cipher cipher = result.getCryptoObject().getCipher(); + + if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { + Intent resultData = new Intent(); + String pin = PINStorage.loadEncryptedPIN(cipher); + resultData.putExtra("newPIN", pin); + resultData.putExtra("confirmPIN", pin); + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) { + Intent resultData = new Intent(); + String pin = PINStorage.loadEncryptedPIN2(cipher); + resultData.putExtra("newPIN2", pin); + resultData.putExtra("confirmPIN2", pin); + setResult(Activity.RESULT_OK, resultData); + finish(); + } else if (mode == Mode.RequestPIN) { + PINStorage.loadEncryptedPIN(cipher); + setResult(Activity.RESULT_OK); + } else if (mode == Mode.RequestPIN2) { + PINStorage.loadEncryptedPIN2(cipher); + setResult(Activity.RESULT_OK); + } + + finish(); + } + + private void startFingerprintReader() { + if (!testFingerPrintSettings()) + return; + + if (!allowFingerprint) + return; + + fingerprintHelper = new FingerprintHelper(RequestPINActivity.this); + mStartFingerprintReaderTask = new StartFingerprintReaderTask(this, fingerprintManager, fingerprintHelper); + mStartFingerprintReaderTask.execute((Void) null); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(), "Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public static class StartFingerprintReaderTask extends AsyncTask { + private KeyStore keyStore; + private Cipher cipher; + + private FingerprintManager.CryptoObject cryptoObject; + + FingerprintManager fingerprintManager; + FingerprintHelper fingerprintHelper; + + RequestPINActivity activity; + + StartFingerprintReaderTask(RequestPINActivity activity, FingerprintManager fingerprintManager, FingerprintHelper fingerprintHelper) { + this.fingerprintManager = fingerprintManager; + this.fingerprintHelper = fingerprintHelper; + this.activity = activity; + } + + @Override + protected Boolean doInBackground(Void... params) { + if (!getKeyStore()) + return false; + + if (!createNewKey(false)) + return false; + + if (!getCipher()) + return false; + + if (!initCipher(Cipher.DECRYPT_MODE)) + return false; + + return initCryptObject(); + } + + @Override + protected void onPostExecute(final Boolean success) { + onCancelled(); + + if (!success) { + doLog("Authentication failed!"); + } else { + fingerprintHelper.startAuth(fingerprintManager, cryptoObject); + doLog("Authenticate using fingerprint!"); + + } + } + + @Override + protected void onCancelled() { + activity.mStartFingerprintReaderTask = null; + } + + private boolean getKeyStore() { + doLog("Getting keystore..."); + try { + keyStore = KeyStore.getInstance(KEYSTORE); + keyStore.load(null); // Create empty keystore + return true; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + public boolean createNewKey(boolean forceCreate) { + doLog("Creating new key..."); + try { + if (forceCreate) + keyStore.deleteEntry(KEY_ALIAS); + + if (!keyStore.containsAlias(KEY_ALIAS)) { + KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE); + + generator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(true) + .build() + ); + + generator.generateKey(); + doLog("Key created."); + } else + doLog("Key exists."); + + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + private boolean getCipher() { + doLog("Getting cipher..."); + try { + cipher = Cipher.getInstance( + KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + + return true; + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCipher(int mode) { + doLog("Initializing cipher..."); + try { + keyStore.load(null); + SecretKey keyspec = (SecretKey) keyStore.getKey(KEY_ALIAS, null); + + if (mode == Cipher.ENCRYPT_MODE) { + cipher.init(mode, keyspec); + } else { + byte[] iv = null; + if (activity.mode == Mode.RequestPIN || activity.mode == Mode.RequestNewPIN || activity.mode == Mode.ConfirmNewPIN) { + iv = PINStorage.loadEncryptedIV(); + } else if (activity.mode == Mode.RequestPIN2 || activity.mode == Mode.RequestNewPIN2 || activity.mode == Mode.ConfirmNewPIN2) { + iv = PINStorage.loadEncryptedIV2(); + } + IvParameterSpec ivspec = new IvParameterSpec(iv); + cipher.init(mode, keyspec, ivspec); + } + + return true; + } catch (KeyPermanentlyInvalidatedException e) { + e.printStackTrace(); + createNewKey(true); // Retry after clearing entry + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCryptObject() { + doLog("Initializing crypt object..."); + try { + cryptoObject = new FingerprintManager.CryptoObject(cipher); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + + } + + public static void doLog(String text) { +// Log.e("FP", text); + } + + @SuppressLint("NewApi") + private boolean testFingerPrintSettings() { + doLog("Testing Fingerprint Settings"); + + KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE); + + assert keyguardManager != null; + if (!keyguardManager.isKeyguardSecure()) { + doLog("User hasn't enabled Lock Screen"); + return false; + } + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { + doLog("User hasn't granted permission to use Fingerprint"); + return false; + } + + if (!fingerprintManager.hasEnrolledFingerprints()) { + doLog("User hasn't registered any fingerprints"); + return false; + } + + doLog("Fingerprint authentication is set.\n"); + + return true; + } + + +} + diff --git a/app/src/main/java/com/tangem/wallet/SavePINActivity.java b/app/src/main/java/com/tangem/wallet/SavePINActivity.java new file mode 100644 index 0000000000..02f519364b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SavePINActivity.java @@ -0,0 +1,543 @@ +package com.tangem.wallet; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Dialog; +import android.app.KeyguardManager; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +//import com.afollestad.materialdialogs.AlertDialogWrapper; + +public class SavePINActivity extends AppCompatActivity implements FingerprintHelper.FingerprintHelperListener { + + private TextView tvPIN; + private CheckBox chkUseFingerprint; + + private ConfirmWithFingerprintTask mConfirmWithFingerprintTask; + + + private KeyStore keyStore; + private Cipher cipher; + private FingerprintManager fingerprintManager; + private FingerprintManager.CryptoObject cryptoObject; + private FingerprintHelper fingerprintHelper; + + private boolean UsePIN2=false; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_save_pin); + + MainActivity.commonInit(getApplicationContext()); + + UsePIN2=getIntent().getBooleanExtra("PIN2", false); + + tvPIN = findViewById(R.id.pin); + OnClickListener onButtonNClick = new OnClickListener() { + @Override + public void onClick(View view) { + tvPIN.setText(String.format("%s%s", tvPIN.getText(),((Button) view).getText())); + } + }; + + if( UsePIN2 ) { + ((TextView) findViewById(R.id.pin_prompt)).setText(R.string.enter_pin2_and_use_fingerprint_to_save_it); + }else{ + ((TextView) findViewById(R.id.pin_prompt)).setText(R.string.enter_pin_and_use_fingerprint_to_save_it); + } + + chkUseFingerprint = (CheckBox) findViewById(R.id.chkUseFingerprint); + + if( UsePIN2 ) { + chkUseFingerprint.setChecked(true); + chkUseFingerprint.setEnabled(false); + }else{ + chkUseFingerprint.setChecked(PINStorage.haveEncryptedPIN()); + chkUseFingerprint.setEnabled(true); + } + + + Button btn0 = findViewById(R.id.btn0); + btn0.setOnClickListener(onButtonNClick); + Button btn1 = findViewById(R.id.btn1); + btn1.setOnClickListener(onButtonNClick); + Button btn2 = findViewById(R.id.btn2); + btn2.setOnClickListener(onButtonNClick); + Button btn3 = findViewById(R.id.btn3); + btn3.setOnClickListener(onButtonNClick); + Button btn4 = findViewById(R.id.btn4); + btn4.setOnClickListener(onButtonNClick); + Button btn5 = findViewById(R.id.btn5); + btn5.setOnClickListener(onButtonNClick); + Button btn6 = findViewById(R.id.btn6); + btn6.setOnClickListener(onButtonNClick); + Button btn7 = findViewById(R.id.btn7); + btn7.setOnClickListener(onButtonNClick); + Button btn8 = findViewById(R.id.btn8); + btn8.setOnClickListener(onButtonNClick); + Button btn9 = findViewById(R.id.btn9); + btn9.setOnClickListener(onButtonNClick); + Button btnBS = findViewById(R.id.btnBackspace); + btnBS.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + String S = tvPIN.getText().toString(); + if (S.length() > 0) { + tvPIN.setText(S.substring(0, S.length() - 1)); + } + } + }); + + Button btnSave = findViewById(R.id.btnSavePIN); + btnSave.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doSavePIN(); + } + }); + + Button btnDelete = findViewById(R.id.btnDeletePIN); + btnDelete.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doDeletePIN(); + } + }); + + } + + @Override + protected void onPause() { + super.onPause(); + + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mConfirmWithFingerprintTask != null) + mConfirmWithFingerprintTask.cancel(true); + } + + @Override + protected void onStop() { + super.onStop(); + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + + if (mConfirmWithFingerprintTask != null) + mConfirmWithFingerprintTask.cancel(true); + } + + + private enum OnConfirmAction {Save, DeleteEncryptedAndSave, Delete} + OnConfirmAction onConfirmAction; + + private void doSavePIN() { + if (mConfirmWithFingerprintTask != null) { + return; + } + + tvPIN.setError(null); + + String pin = tvPIN.getText().toString(); + + boolean cancel = false; + View focusView = null; + + if (TextUtils.isEmpty(pin)) { + tvPIN.setError(getString(R.string.error_empty_pin)); + focusView = tvPIN; + cancel = true; + } + + if (cancel) { + focusView.requestFocus(); + } else { + + if( UsePIN2 ) + { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + + onConfirmAction = OnConfirmAction.Save; + + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + }else { + if (chkUseFingerprint.isChecked() || PINStorage.haveEncryptedPIN()) { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + + if (chkUseFingerprint.isChecked()) { + onConfirmAction = OnConfirmAction.Save; + } else { + onConfirmAction = OnConfirmAction.DeleteEncryptedAndSave; + } + + // Show a progress spinner, and kick off a background task to + // perform the user login attempt. + //showProgress(true); + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + } else { + PINStorage.savePIN(tvPIN.getText().toString()); + finish(); + } + } + } + } + + private void doDeletePIN() { + if( UsePIN2 ) + { + if (PINStorage.haveEncryptedPIN2()) { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + onConfirmAction = OnConfirmAction.Delete; + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + } else { + tvPIN.setText(""); + finish(); + } + }else { + if (chkUseFingerprint.isChecked() || PINStorage.haveEncryptedPIN()) { + if (!testFingerPrintSettings()) { + tvPIN.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000); + return; + } + onConfirmAction = OnConfirmAction.Delete; + mConfirmWithFingerprintTask = new ConfirmWithFingerprintTask(); + mConfirmWithFingerprintTask.execute((Void) null); + } else { + tvPIN.setText(""); + PINStorage.deletePIN(); + finish(); + } + } + } + + + @Override + public void authenticationFailed(String error) { + print(error); + } + + @TargetApi(Build.VERSION_CODES.M) + @Override + public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { + print("Authentication succeeded!"); + cipher = result.getCryptoObject().getCipher(); + + switch (onConfirmAction) { + case Save: + String textToEncrypt = tvPIN.getText().toString(); + if( UsePIN2 ) + { + PINStorage.saveEncryptedPIN2(cipher, textToEncrypt); + }else { + PINStorage.saveEncryptedPIN(cipher, textToEncrypt); + } + print(R.string.pin_save_success); + break; + case Delete: + if( UsePIN2 ) + { + PINStorage.deleteEncryptedPIN2(); + }else { + PINStorage.deleteEncryptedPIN(); + PINStorage.deletePIN(); + } + tvPIN.setText(""); + break; + case DeleteEncryptedAndSave: + if( UsePIN2 ) + { + PINStorage.deleteEncryptedPIN2(); + PINStorage.saveEncryptedPIN2(cipher, tvPIN.getText().toString()); + }else { + PINStorage.deleteEncryptedPIN(); + PINStorage.savePIN(tvPIN.getText().toString()); + } + break; + } + dFingerPrintConfirmation.dismiss(); + finish(); + } + + private class ConfirmWithFingerprintTask extends AsyncTask { + ConfirmWithFingerprintTask() { + fingerprintHelper = new FingerprintHelper(SavePINActivity.this); + } + + @Override + protected Boolean doInBackground(Void... params) { + if (!getKeyStore()) + return false; + + if (!createNewKey(false)) + return false; + + if (!getCipher()) + return false; + + return initCipher(Cipher.ENCRYPT_MODE) && initCryptObject(); + + } + + @Override + protected void onPostExecute(final Boolean success) { + onCancelled(); + + if (!success) { + Toast.makeText(getBaseContext(), R.string.pin_save_fail, Toast.LENGTH_LONG).show(); + } else { + print("Confirm PIN action using fingerprint!"); + fingerprintHelper.startAuth(fingerprintManager, cryptoObject); + CreateFingerPrintConfirmationDialog(); + } + } + + @Override + protected void onCancelled() { + mConfirmWithFingerprintTask = null; + if (dFingerPrintConfirmation != null) { + dFingerPrintConfirmation.cancel(); + } + } + } + + public void print(String text) { +// Log.e("FP", text); + } + + public void print(int id) { + print(getString(id)); + } + + @SuppressLint("NewApi") + private boolean testFingerPrintSettings() { + print("Testing Fingerprint Settings"); + + KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE); + + if (!keyguardManager.isKeyguardSecure()) { + print("User hasn't enabled Lock Screen"); + Toast.makeText(getBaseContext(), "User hasn't enabled Lock Screen", Toast.LENGTH_LONG).show(); + return false; + } + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { + print("User hasn't granted permission to use Fingerprint"); + Toast.makeText(getBaseContext(), "User hasn't granted permission to use Fingerprint", Toast.LENGTH_LONG).show(); + return false; + } + + if (!fingerprintManager.hasEnrolledFingerprints()) { + print("User hasn't registered any fingerprints"); + Toast.makeText(getBaseContext(), "User hasn't registered any fingerprints", Toast.LENGTH_LONG).show(); + return false; + } + + print("Fingerprint authentication is set.\n"); + + return true; + } + + private boolean getKeyStore() { + print("Getting keystore..."); + try { + keyStore = KeyStore.getInstance(RequestPINActivity.KEYSTORE); + keyStore.load(null); // Create empty keystore + return true; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + public boolean createNewKey(boolean forceCreate) { + print("Creating new key..."); + try { + if (forceCreate) + keyStore.deleteEntry(RequestPINActivity.KEY_ALIAS); + + if (!keyStore.containsAlias(RequestPINActivity.KEY_ALIAS)) { + KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, RequestPINActivity.KEYSTORE); + + generator.init(new KeyGenParameterSpec.Builder(RequestPINActivity.KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(true) + .build() + ); + + generator.generateKey(); + print("Key created."); + } else + print("Key exists."); + + return true; + } catch (Exception e) { + print(e.getMessage()); + } + + return false; + } + + private boolean getCipher() { + print("Getting cipher..."); + try { + cipher = Cipher.getInstance( + KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + + return true; + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCipher(int mode) { + print("Initializing cipher..."); + try { + keyStore.load(null); + SecretKey keyspec = (SecretKey) keyStore.getKey(RequestPINActivity.KEY_ALIAS, null); + + if (mode == Cipher.ENCRYPT_MODE) { + cipher.init(mode, keyspec); + } else { + byte[] iv = PINStorage.loadEncryptedIV(); + IvParameterSpec ivspec = new IvParameterSpec(iv); + cipher.init(mode, keyspec, ivspec); + } + + return true; + } catch (KeyPermanentlyInvalidatedException e) { + e.printStackTrace(); + createNewKey(true); // Retry after clearing entry + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private boolean initCryptObject() { + print("Initializing crypt object..."); + try { + cryptoObject = new FingerprintManager.CryptoObject(cipher); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + Dialog dFingerPrintConfirmation = null; + + private void CreateFingerPrintConfirmationDialog() { +// final AlertDialogWrapper.Builder b = new AlertDialogWrapper.Builder(this); +// switch (onConfirmAction) +// { +// case Save: +// if( UsePIN2) { +// b.setTitle("Confirm new PIN2 saving "); +// }else{ +// b.setTitle("Confirm new PIN saving "); +// } +// break; +// case Delete: +// if( UsePIN2 ) { +// b.setTitle("Confirm PIN2 deleting"); +// }else{ +// b.setTitle("Confirm PIN deleting"); +// } +// break; +// case DeleteEncryptedAndSave: +// if( UsePIN2 ) { +// b.setTitle("Confirm deleting old saved PIN2"); +// }else { +// b.setTitle("Confirm deleting old saved PIN"); +// } +// break; +// } +// View view = getLayoutInflater().inflate(R.layout.dialog_fingerprint_confirmation, null); +// b.setView(view); + +// dFingerPrintConfirmation = b.show(); +// dFingerPrintConfirmation.setOnCancelListener(new DialogInterface.OnCancelListener() { +// @Override +// public void onCancel(DialogInterface dialog) { +// fingerprintHelper.cancel(); +// print("Cancel fingerprint confirmation"); +// } +// }); + } +} + diff --git a/app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java b/app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java new file mode 100644 index 0000000000..635daf6791 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SelectBlockchainActivity.java @@ -0,0 +1,31 @@ +package com.tangem.wallet; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class SelectBlockchainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_select_blockchain); + +// Spinner spBlockchain = (Spinner) findViewById(R.id.spBlockchain); +// ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, Blockchain.values()); +// spBlockchain.setAdapter(adapter); +// spBlockchain.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { +// @Override +// public void onItemSelected(AdapterView parent, View view, int position, long id) { +// Intent intent = new Intent(); +// intent.putExtra("blockchain", Blockchain.values()[position].toString()); +// setResult(RESULT_OK, intent); +// finish(); +// } +// +// @Override +// public void onNothingSelected(AdapterView parent) { +// +// } +// }); + } +} diff --git a/app/src/main/java/com/tangem/wallet/SendTransactionActivity.java b/app/src/main/java/com/tangem/wallet/SendTransactionActivity.java new file mode 100644 index 0000000000..84c64e2f88 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SendTransactionActivity.java @@ -0,0 +1,208 @@ +package com.tangem.wallet; + +import android.content.Intent; +import android.os.AsyncTask; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.widget.ProgressBar; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigInteger; +import java.util.List; + +public class SendTransactionActivity extends AppCompatActivity { + + ProgressBar progressBar; + private Tangem_Card mCard; + private String tx; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_send_transaction); + + MainActivity.commonInit(getApplicationContext()); + + progressBar = findViewById(R.id.progressBar); + + Intent intent = getIntent(); + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(intent.getExtras().getBundle("Card")); + tx = intent.getStringExtra("TX"); + + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); + if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { + ETHRequestTask task = new ETHRequestTask(mCard.getBlockchain()); + Infura_Request req = Infura_Request.SendTransaction(mCard.getWallet(), tx); + req.setID(67); + req.setBlockchain(mCard.getBlockchain()); + task.execute(req); + } else if (mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet ) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + else if (mCard.getBlockchain() == Blockchain.BitcoinCash || mCard.getBlockchain() == Blockchain.BitcoinCashTestNet ) { + String nodeAddress = engine.GetNode(mCard); + int nodePort = engine.GetNodePort(mCard); + ConnectTask connectTask = new ConnectTask(nodeAddress, nodePort); + connectTask.execute(Electrum_Request.Broadcast(mCard.getWallet(), tx)); + } + + } + + @Override + public boolean onKeyDown(int keycode, KeyEvent e) { + switch (keycode) { + case KeyEvent.KEYCODE_BACK: + Toast.makeText(getBaseContext(),"Please wait while the payment is sent...",Toast.LENGTH_LONG).show(); + return true; + } + + return super.onKeyDown(keycode, e); + } + + void FinishWithError(String Message) { + Intent intent = new Intent(); + intent.putExtra("message", "Failed to send transaction. Try again."); + setResult(MainActivity.RESULT_CANCELED, intent); + finish(); + } + + void FinishWithSuccess() { + Intent intent = new Intent(); + intent.putExtra("message", "Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while"); + setResult(MainActivity.RESULT_OK, intent); + finish(); + } + + private class ETHRequestTask extends Infura_Task { + ETHRequestTask(Blockchain blockchain){ + super(blockchain); + } + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + for (Infura_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Infura_Request.METHOD_ETH_SendRawTransaction)) { + try { + String hashTX = ""; + try { + String tmp = request.getResultString(); + hashTX = tmp; + }catch(JSONException e) + { + JSONObject msg = request.getAnswer(); + JSONObject err = msg.getJSONObject("error"); + hashTX = err.getString("message"); + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + FinishWithError(hashTX); + return; + } + + try { + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + BigInteger nonce = mCard.GetConfirmTXCount(); + nonce.add(BigInteger.valueOf(1)); + mCard.SetConfirmTXCount(nonce); + Log.e("TX_RESULT", hashTX); + FinishWithSuccess(); + }catch(Exception e) + { + FinishWithError(hashTX); + } + + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(e.toString()); + } + } + } else if (request.error != null) { + FinishWithError(request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + FinishWithError(e.toString()); + } + } + } + } + + private class ConnectTask extends Electrum_Task { + public ConnectTask(String host, int port) { + super(host, port); + } + + public ConnectTask(String host, int port, SharedData sharedData) { + super(host, port, sharedData); + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + CoinEngine engine = CoinEngineFactory.Create(Blockchain.Bitcoin); + + for (Electrum_Request request : requests) { + try { + if (request.error == null) { + if (request.isMethod(Electrum_Request.METHOD_SendTransaction)) { + try { + String hashTX = request.getResultString(); + + try + { + LastSignStorage.setLastMessage(mCard.getWallet(), hashTX); + if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { + hashTX = hashTX.substring(2); + } + BigInteger bigInt = new BigInteger(hashTX, 16); //TODO: очень плохой способ + LastSignStorage.setTxWasSend(mCard.getWallet()); + LastSignStorage.setLastMessage(mCard.getWallet(), ""); + Log.e("TX_RESULT", hashTX); + FinishWithSuccess(); + }catch(Exception e) + { + engine.SwitchNode(null); + FinishWithError(hashTX); + return; + } + + } catch (JSONException e) { + e.printStackTrace(); + engine.SwitchNode(null); + FinishWithError(e.toString()); + } + } + } else if (request.error != null) { + engine.SwitchNode(null); + FinishWithError(request.error); + } + } catch (JSONException e) { + e.printStackTrace(); + engine.SwitchNode(null); + FinishWithError(e.toString()); + } + } + + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/SharedData.java b/app/src/main/java/com/tangem/wallet/SharedData.java new file mode 100644 index 0000000000..45fcfa5def --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SharedData.java @@ -0,0 +1,22 @@ +package com.tangem.wallet; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by Ilia on 12.04.2018. + */ + + +public class SharedData +{ + public static int COUNT_REQUEST = 3; + public AtomicInteger requestCounter; + public int allRequest; + public AtomicInteger errorRequest; + public SharedData(int requstCount) + { + allRequest = requstCount; + errorRequest = new AtomicInteger(0); + requestCounter = new AtomicInteger(0); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/SignPaymentActivity.java b/app/src/main/java/com/tangem/wallet/SignPaymentActivity.java new file mode 100644 index 0000000000..1f5c582659 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SignPaymentActivity.java @@ -0,0 +1 @@ +package com.tangem.wallet; import android.app.Activity; import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.Color; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.tangem.cardReader.CardProtocol; import com.tangem.cardReader.NfcManager; import com.tangem.cardReader.Util; public class SignPaymentActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { private static final int REQUEST_CODE_SEND_PAYMENT = 1; public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; private Tangem_Card mCard; private TextView tvCardID; private NfcManager mNfcManager; private static final String logTag = "SignPayment"; private ProgressBar progressBar; private SignPaymentTask signPaymentTask; private String amountStr; private String feeStr; private String outAddressStr; private boolean lastReadSuccess = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sign_payment); MainActivity.commonInit(getApplicationContext()); mCard = new Tangem_Card(getIntent().getStringExtra("UID")); mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); amountStr = getIntent().getStringExtra("Amount"); feeStr = getIntent().getStringExtra("Fee"); outAddressStr = getIntent().getStringExtra("Wallet"); tvCardID = findViewById(R.id.tvCardID); tvCardID.setText(mCard.getCIDDescription()); mNfcManager = new NfcManager(this, this); progressBar = findViewById(R.id.progressBar); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); } @Override public void onTagDiscovered(Tag tag) { try { // get IsoDep handle and run cardReader thread final IsoDep isoDep = IsoDep.get(tag); if (isoDep == null) { throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); } byte UID[] = tag.getId(); String sUID = Util.byteArrayToHexString(UID); Log.v(logTag, "UID: " + sUID); if (sUID.equals(mCard.getUID())) { if (lastReadSuccess) { isoDep.setTimeout(mCard.getPauseBeforePIN2() + 5000); } else { isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); } signPaymentTask = new SignPaymentTask(isoDep, this, amountStr, feeStr, outAddressStr); signPaymentTask.start(); } else { Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); mNfcManager.IgnoreTag(isoDep.getTag()); return; } } catch (Exception e) { e.printStackTrace(); } } @Override public void onResume() { super.onResume(); mNfcManager.onResume(); } @Override public void onPause() { mNfcManager.onPause(); if (signPaymentTask != null) { signPaymentTask.cancel(true); } super.onPause(); } @Override public void onStop() { // dismiss enable NFC dialog mNfcManager.onStop(); if (signPaymentTask != null) { signPaymentTask.cancel(true); } super.onStop(); } // @Override // public Dialog CreateNFCDialog(int id, AlertDialogWrapper.Builder builder, LayoutInflater li) { // return mNfcManager.onCreateDialog(id, builder, li); // } private class SignPaymentTask extends Thread { String txAmount = ""; String txFee = ""; public void SetTransactionValue(String amount, String fee) { txAmount = amount; txFee = fee; } private String txOutAddress; IsoDep mIsoDep; CardProtocol.Notifications mNotifications; private boolean isCancelled = false; public SignPaymentTask(IsoDep isoDep, CardProtocol.Notifications notifications, String amount, String fee, String outAddress) { mIsoDep = isoDep; mNotifications = notifications; txOutAddress = outAddress; SetTransactionValue(amount, fee); } @Override public void run() { if (mIsoDep == null) { return; } CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); mNotifications.OnReadStart(protocol); try { // for Samsung's bugs - // Workaround for the Samsung Galaxy S5 (since the // first connection always hangs on transceive). int timeout = mIsoDep.getTimeout(); mIsoDep.connect(); mIsoDep.close(); mIsoDep.connect(); mIsoDep.setTimeout(timeout); try { mNotifications.OnReadProgress(protocol, 5); Log.i("SignPaymentTask", "[-- Start sign payment --]"); if (isCancelled) return; protocol.run_VerifyCard(); Log.i("SignPaymentTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); mNotifications.OnReadProgress(protocol, 30); if (isCancelled) return; // // if (mCard.getBlockchain() == Blockchain.Ethereum) { // SignETH_TX(protocol); // } else { // SignBTC_TX(protocol); // } CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); if (engine != null) { if (mCard.getPauseBeforePIN2() > 0) { mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); } byte[] tx; // try { tx = engine.Sign(txFee, txAmount, txOutAddress, mCard, protocol); // } // finally { // mNotifications.OnReadWait(0); // } if (tx != null) { String txStr = BTCUtils.toHex(tx); if (mCard.getBlockchain() == Blockchain.Ethereum || mCard.getBlockchain() == Blockchain.EthereumTestNet || mCard.getBlockchain() == Blockchain.Token) { txStr = String.format("0x%s", txStr); } LastSignStorage.setLastTX(mCard.getWallet(), txStr); Intent intent = new Intent(getBaseContext(), SendTransactionActivity.class); intent.putExtra("UID", mCard.getUID()); intent.putExtra("Card", mCard.getAsBundle()); intent.putExtra("TX", txStr); startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT); } } mNotifications.OnReadProgress(protocol, 100); if (isCancelled) return; } finally { mNfcManager.IgnoreTag(mIsoDep.getTag()); } } catch (CardProtocol.TangemException_InvalidPIN e) { e.printStackTrace(); protocol.setError(e); } catch (Exception e) { e.printStackTrace(); protocol.setError(e); } finally { Log.i("SignPaymentTask", "[-- Finish sign payment --]"); mNotifications.OnReadFinish(protocol); } } public void cancel(Boolean AllowInterrupt) { try { if (this.isAlive()) { isCancelled = true; join(500); } if (this.isAlive() && AllowInterrupt) { interrupt(); mNotifications.OnReadCancel(); } } catch (Exception e) { e.printStackTrace(); } } } public void OnReadStart(CardProtocol cardProtocol) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setVisibility(View.VISIBLE); progressBar.setProgress(5); } }); } public void OnReadFinish(final CardProtocol cardProtocol) { signPaymentTask = null; if (cardProtocol != null) { if (cardProtocol.getError() == null) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setProgress(100); progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); } }); } else { lastReadSuccess = false; if (cardProtocol.getError().getClass().equals(CardProtocol.TangemException_InvalidPIN.class)) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setProgress(100); progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); } }); progressBar.postDelayed(new Runnable() { @Override public void run() { try { progressBar.setProgress(0); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); Intent intent = new Intent(); intent.putExtra("message", "Cannot sign transaction. Make sure you enter correct PIN2!"); intent.putExtra("UID", cardProtocol.getCard().getUID()); intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); setResult(RESULT_INVALID_PIN, intent); finish(); } catch (Exception e) { e.printStackTrace(); } } }, 500); return; } else { progressBar.post(new Runnable() { @Override public void run() { if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { if (!NoExtendedLengthSupportDialog.allreadyShowed) { new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); } } else { Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); } progressBar.setProgress(100); progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); } }); } } } progressBar.postDelayed(new Runnable() { @Override public void run() { try { progressBar.setProgress(0); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); } catch (Exception e) { e.printStackTrace(); } } }, 500); } public void OnReadProgress(CardProtocol protocol, final int progress) { progressBar.post(new Runnable() { @Override public void run() { progressBar.setProgress(progress); } }); } public void OnReadCancel() { signPaymentTask = null; progressBar.postDelayed(new Runnable() { @Override public void run() { try { progressBar.setProgress(0); progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); progressBar.setVisibility(View.INVISIBLE); } catch (Exception e) { e.printStackTrace(); } } }, 500); } @Override public void OnReadWait(final int msec) { WaitSecurityDelayDialog.OnReadWait(this, msec); } @Override public void OnReadBeforeRequest(int timeout) { WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); } @Override public void OnReadAfterRequest() { WaitSecurityDelayDialog.onReadAfterRequest(this); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: Intent intent = new Intent(); intent.putExtra("message", "Operation canceled by user"); setResult(Activity.RESULT_CANCELED, intent); finish(); return true; } return super.onKeyDown(keyCode, event); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_SEND_PAYMENT) { setResult(resultCode, data); finish(); return; } super.onActivityResult(requestCode, resultCode, data); } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/SwapPINActivity.java b/app/src/main/java/com/tangem/wallet/SwapPINActivity.java new file mode 100644 index 0000000000..94b2342ee5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/SwapPINActivity.java @@ -0,0 +1,326 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.IsoDep; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; +import com.tangem.cardReader.Util; + +public class SwapPINActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback, CardProtocol.Notifications { + + public static final int RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER; + private Tangem_Card mCard; + private NfcManager mNfcManager; + private static final String logTag = "SwapPIN"; + private ProgressBar progressBar; + private SwapPINTask swapPinTask; + + private String newPIN, newPIN2; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_swap_pin); + + MainActivity.commonInit(getApplicationContext()); + + mCard = new Tangem_Card(getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getIntent().getExtras().getBundle("Card")); + + newPIN = getIntent().getStringExtra("newPIN"); + newPIN2 = getIntent().getStringExtra("newPIN2"); + + TextView tvCardID = findViewById(R.id.tvCardID); + tvCardID.setText(mCard.getCIDDescription()); + + mNfcManager = new NfcManager(this, this); + + progressBar = findViewById(R.id.progressBar); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + // get IsoDep handle and run cardReader thread + final IsoDep isoDep = IsoDep.get(tag); + if (isoDep == null) { + throw new CardProtocol.TangemException(getString(R.string.wrong_tag_err)); + } + byte UID[] = tag.getId(); + String sUID = Util.byteArrayToHexString(UID); + Log.v(logTag, "UID: " + sUID); + + if (sUID.equals(mCard.getUID())) { + isoDep.setTimeout(mCard.getPauseBeforePIN2() + 65000); + swapPinTask = new SwapPINTask(isoDep, this); + swapPinTask.start(); + } else { + Log.d(logTag, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")"); + mNfcManager.IgnoreTag(isoDep.getTag()); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + mNfcManager.onPause(); + if (swapPinTask != null) { + swapPinTask.cancel(true); + } + super.onPause(); + } + + @Override + public void onStop() { + // dismiss enable NFC dialog + mNfcManager.onStop(); + if (swapPinTask != null) { + swapPinTask.cancel(true); + } + super.onStop(); + } + + private class SwapPINTask extends Thread { + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private boolean isCancelled = false; + + SwapPINTask(IsoDep isoDep, CardProtocol.Notifications notifications) { + mIsoDep = isoDep; + mNotifications = notifications; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + CardProtocol protocol = new CardProtocol(getBaseContext(), mIsoDep, mCard, mNotifications); + + mNotifications.OnReadStart(protocol); + try { + + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + + mNotifications.OnReadProgress(protocol, 5); + + Log.i("SwapTask", "[-- Start swap pin --]"); + + if (isCancelled) return; + + if (mCard.getPauseBeforePIN2() > 0) { + mNotifications.OnReadWait(mCard.getPauseBeforePIN2()); + } + +// try { + protocol.run_SwapPIN(PINStorage.getPIN2(), newPIN, newPIN2, false); + protocol.setPIN(newPIN); + mCard.setPIN(newPIN); +// } finally { +// mNotifications.OnReadWait(0); +// } + + mNotifications.OnReadProgress(protocol, 50); + + protocol.run_Read(); + + mNotifications.OnReadProgress(protocol, 100); + + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("SwapPINTask", "[-- Finish purge --]"); + mNotifications.OnReadFinish(protocol); + } + } + + public void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + + public void OnReadStart(CardProtocol cardProtocol) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + progressBar.setProgress(5); + } + }); + } + + public void OnReadFinish(final CardProtocol cardProtocol) { + + swapPinTask = null; + + if (cardProtocol != null) { + if (cardProtocol.getError() == null) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.GREEN)); + Intent intent = new Intent(); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } else if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + Intent intent = new Intent(); + intent.putExtra("message", "Cannot change PIN(s). Make sure you enter correct PIN2!"); + intent.putExtra("UID", cardProtocol.getCard().getUID()); + intent.putExtra("Card", cardProtocol.getCard().getAsBundle()); + setResult(RESULT_INVALID_PIN, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + return; + } else { + + progressBar.post(new Runnable() { + @Override + public void run() { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(getFragmentManager(), "NoExtendedLengthSupportDialog"); + } + } else { + Toast.makeText(getBaseContext(), "Try to scan again", Toast.LENGTH_LONG).show(); + } + progressBar.setProgress(100); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.RED)); + } + }); + + } + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + } + + public void OnReadProgress(CardProtocol protocol, final int progress) { + progressBar.post(new Runnable() { + @Override + public void run() { + progressBar.setProgress(progress); + } + }); + } + + public void OnReadCancel() { + + swapPinTask = null; + + progressBar.postDelayed(new Runnable() { + @Override + public void run() { + try { + progressBar.setProgress(0); + progressBar.setProgressTintList(ColorStateList.valueOf(Color.DKGRAY)); + progressBar.setVisibility(View.INVISIBLE); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + @Override + public void OnReadWait(final int msec) { + WaitSecurityDelayDialog.OnReadWait(this, msec); + } + + @Override + public void OnReadBeforeRequest(int timeout) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout); + } + + @Override + public void OnReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this); + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/Tangem_Card.java b/app/src/main/java/com/tangem/wallet/Tangem_Card.java new file mode 100644 index 0000000000..589761a3ae --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Tangem_Card.java @@ -0,0 +1,1365 @@ +package com.tangem.wallet; + +import android.os.Bundle; +import android.util.Log; + +import com.google.common.base.Strings; +import com.tangem.cardReader.SettingsMask; +import com.tangem.cardReader.Util; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.StringJoiner; + +/** + * Created by dvol on 16.07.2017. + */ + +public class Tangem_Card { + private byte[] CID; + private Status status; + private String wallet; + private String UID; + private String error; + private String message; + private String balanceDecimal; + private String balanceDecimalAlter = ""; + private Long balanceConfirmed, balanceUnconfirmed; + private String blockchainID; + private Manufacturer manufacturer = Manufacturer.Unknown; + private boolean manufacturerConfirmed = false; + private int maxSignatures; + private int remainingSignatures; + private String PIN; + private byte[] pbWalletKey = null; + private byte[] pbCardKey = null; + private byte[] pbWalletKeyRar = null; + private Date dtPersonalization = null; + private float rate = 0; + private float rateAlter = 0; + +// public static final int SettingsMask_IsReusable = 0x0001; +// public static final int SettingsMask_UseRecovery = 0x0002; +// public static final int SettingsMask_UseBlock = 0x0004; +// +// public static final int SettingsMask_AllowSwapPIN = 0x0010; +// public static final int SettingsMask_AllowSwapPIN2 = 0x0020; +// public static final int SettingsMask_UseCVC = 0x0040; +// +// public static final int SettingsMask_UseOneCommandAtTime = 0x100; +// public static final int SettingsMask_UseNDEF = 0x200; +// public static final int SettingsMask_UseDynamicNDEF = 0x400; + + private BigInteger countConfirmTX = null; + + public BigInteger GetConfirmTXCount() { + if (countConfirmTX == null) { + countConfirmTX = BigInteger.valueOf(0); + } + return countConfirmTX; + } + + public void SetConfirmTXCount(BigInteger count) { + countConfirmTX = count; + } + + public float getRate() { + return rate; + } + + public float getRateAlter() { + return rateAlter; + } + + public void setRate(float rate) { + this.rate = rate; + } + + public void setRateAlter(float rate) { + this.rateAlter = rate; + } + + public String getBlockchainID() { + return blockchainID; + } + + public Blockchain getBlockchain() { + //TODO: hard fork only for test + //return Blockchain.Ethereum; + return Blockchain.fromId(blockchainID); + } + + public void setBlockchainID(String blockchainID) { + this.blockchainID = blockchainID; + } + + public void addTokenToBlockchainName() { + String token = getTokenSymbol(); + if (Strings.isNullOrEmpty(token)) + return; + String oldName = getBlockchain().getOfficialName(); + String newName = String.format("%s (%s)", oldName, token); + setBlockchainName(newName); + } + + private String blochchainName = ""; + + public void setBlockchainName(String name) { + blochchainName = name; + } + + + public String getBlockchainName() { + if (Strings.isNullOrEmpty(blochchainName)) + return getBlockchain().getOfficialName(); + return blochchainName; + } + + public void setBlockchainIDFromCard(String blockchainID) { + + //TODO: ONLY FOR TEST + +// if(Blockchain.fromId(blockchainID) == Blockchain.Bitcoin) +// { +// this.blockchainID = "BCH"; +// return; +// } +// +// if(Blockchain.fromId(blockchainID) == Blockchain.BitcoinTestNet) +// { +// this.blockchainID = "BCH/test"; +// return; +// } + + + if (Blockchain.fromId(blockchainID) != Blockchain.Ethereum && Blockchain.fromId(blockchainID) != Blockchain.EthereumTestNet) + this.blockchainID = blockchainID; + + if (isToken()) { + this.blockchainID = Blockchain.Token.getID(); + addTokenToBlockchainName(); + } else { + this.blockchainID = blockchainID; + } + + + } + + public void setWalletPublicKey(byte[] publicKey) { + pbWalletKey = publicKey; + } + + public void setWalletPublicKeyRar(byte[] publicKey) { + pbWalletKeyRar = publicKey; + } + + public byte[] getWalletPublicKey() { + return pbWalletKey; + } + + public byte[] getWalletPublicKeyRar() { + return pbWalletKeyRar; + } + + private boolean walletPublicKeyValid = false; + + public void setWalletPublicKeyValid(boolean walletPublicKeyValid) { + this.walletPublicKeyValid = walletPublicKeyValid; + } + + public boolean isWalletPublicKeyValid() { + return walletPublicKeyValid; + } + + + public void setCardPublicKey(byte[] publicKey) { + pbCardKey = publicKey; + } + + public byte[] getCardPublicKey() { + return pbCardKey; + } + + private boolean cardPublicKeyValid = false; + + public void setCardPublicKeyValid(boolean cardPublicKeyValid) { + this.cardPublicKeyValid = cardPublicKeyValid; + } + + public boolean isCardPublicKeyValid() { + return cardPublicKeyValid; + } + + public Double AmountFromInternalUnits(Long internalAmount) { + return ((double) internalAmount) / getBlockchain().getMultiplier(); + } + + private Double AmountFromInternalUnits(Integer internalAmount) { + return ((double) internalAmount) / getBlockchain().getMultiplier(); + } + + // TODO разобраться с балансами, привести к единому интерфейсу + public Long InternalUnitsFromString(String caption) { + try { + return FormatUtil.ConvertStringToLong(caption); + } catch (Exception e) { + return null; + } + } + + + public String getAmountDescription(Double amount) { + if (amount < 10) { + amount *= 1000.0; //todo: really? + //String pattern = "#0.000"; // If you like 4 zeros + //DecimalFormat myFormatter = new DecimalFormat(pattern); + //String output = myFormatter.format(amount); + + String output = FormatUtil.DoubleToString(amount); + + return output + " m" + getBlockchain().getCurrency(); + + //return String.format("%.3f m%s", amount, getBlockchain().getCurrency()); + } else { + //return String.format("%.3f %s", amount, getBlockchain().getCurrency()); + String output = FormatUtil.DoubleToString(amount); + return output + " " + getBlockchain().getCurrency(); + + } + } + + public String getAmountEquivalentDescription(Double amount) { + if (getBlockchain() == Blockchain.Ethereum || getBlockchain() == Blockchain.EthereumTestNet || getBlockchain() == Blockchain.Token) { + return EthEngine.getAmountEquivalentDescriptionETH(amount, rate); + } + + return BtcEngine.getAmountEquivalentDescriptionBTC(amount, rate); + } + + public boolean getAmountEquivalentDescriptionAvailable() { + return rate > 0; + } + + public String getAmountInGwei(String amount) { + BigInteger d = new BigInteger(amount, 10); + BigInteger m = d.divide(BigInteger.valueOf(1000000000L)); + return m.toString(10); + } + + public Manufacturer getManufacturer() { + return manufacturer; + } + + public boolean isManufacturerConfirmed() { + return manufacturerConfirmed; + } + + public void setManufacturer(Manufacturer manufacturer, boolean verified) { + if (this.manufacturer == manufacturer) { + this.manufacturerConfirmed |= verified; + } else { + this.manufacturer = manufacturer; + this.manufacturerConfirmed = verified; + } + } + + public int getRemainingSignatures() { + return remainingSignatures; + } + + public void setRemainingSignatures(int remainingSignatures) { + this.remainingSignatures = remainingSignatures; + } + + public String getTransactionList() { + try { + StringBuffer result = new StringBuffer(); + if (hasUnspentInfo()) { + if (mUnspentTransactions.size() > 0) { + + for (int i = 0; i < mUnspentTransactions.size(); ++i) { + if (i != 0) { + result.append(" \n"); + } + result.append(String.valueOf(i + 1)); + result.append(": "); + long unix_time = GetTimestamp(mUnspentTransactions.get(i).Height); + + if (unix_time < UNIX_TIME_2017_01_01) + return "-- -- --"; + unix_time = unix_time * 1000L; + + Date dt = new Date(unix_time); + //java.util.Calendar cd = Calendar.getInstance(); + //cd.setTimeInMillis(unix_time); + + //result.append(String.format("%02d/%02d/%04d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR))); + + result.append(Util.formatDate(dt)); + + + result.append(" - Amount: "); + result.append(getAmountDescription(AmountFromInternalUnits(mUnspentTransactions.get(i).Amount))); + //result.append(String.valueOf((double) mUnspentTransactions.get(i).Amount / 100000)); + //result.append(",\nTx: "); This is only for developers + //result.append(String.valueOf(mUnspentTransactions.get(i).txID)); + + } + +// if (hasHistoryInfo()) { +// for (int i = 0; i < mHistoryTransactions.size(); ++i) { +// result.append(" \n"); +// result.append("O"); +// result.append(String.valueOf(i + 1)); +// result.append(": "); +// long unix_time = GetTimestamp(mHistoryTransactions.get(i).Height); +// unix_time = unix_time * 1000L; +// +// java.util.Calendar cd = Calendar.getInstance(); +// cd.setTimeInMillis(unix_time); +// +// result.append(String.format("%02d/%02d/%04d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR))); +// +// +// } +// } + } else { + result.append("None"); + } + } else { + result.append("-- -- --"); + } + if (balanceUnconfirmed != 0) { + result.append("awaiting confirmation..."); + } + return result.toString(); + } catch (Exception e) { + return "?"; + } + } + + public String getInputsDescription() { + + try { + StringBuffer result = new StringBuffer(); + if (hasUnspentInfo()) { + if (mUnspentTransactions.size() == 1) { + // + if (balanceUnconfirmed != 0) { + result.append(getAmountDescription(AmountFromInternalUnits(mUnspentTransactions.get(0).Amount))); + } else { + result.append("1 unspent"); + } + long unix_time = GetTimestamp(mUnspentTransactions.get(0).Height); + + if (unix_time < UNIX_TIME_2017_01_01) { + + } else { + result.append(", "); + unix_time = unix_time * 1000L; + result.append(Util.formatDateTime(new Date(unix_time))); + } + + +// java.util.Calendar cd = Calendar.getInstance(); +// cd.setTimeInMillis(unix_time); + + //result.append(String.format(" at %02d/%02d/%04d %02d:%02d:%02d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR), cd.get(Calendar.HOUR), cd.get(Calendar.MINUTE), cd.get(Calendar.SECOND))); + + + if (balanceUnconfirmed != 0) { + result.append("\nawaiting confirmation..."); + } + } else if (mUnspentTransactions.size() > 0) { + long minDate = GetTimestamp(mUnspentTransactions.get(0).Height) * 1000L; + long maxDate = minDate; + int unspentAmount = mUnspentTransactions.get(0).Amount; + + for (int i = 1; i < mUnspentTransactions.size(); ++i) { + long unix_time = GetTimestamp(mUnspentTransactions.get(i).Height); + unix_time = unix_time * 1000L; + + if (unix_time < minDate) minDate = unix_time; + if (unix_time > maxDate) maxDate = unix_time; + unspentAmount += mUnspentTransactions.get(i).Amount; + } + + Calendar cd = Calendar.getInstance(); + cd.setTimeInMillis(minDate); + if (balanceUnconfirmed != 0) { + result.append(getAmountDescription(AmountFromInternalUnits(unspentAmount))); + result.append(" in "); + } + result.append(String.valueOf(mUnspentTransactions.size())); + result.append(" unspents\n"); + + result.append(String.format("between %s\n", Util.formatDateTime(new Date(minDate)))); + + cd.setTimeInMillis(maxDate); + result.append(String.format("and %s", Util.formatDateTime(new Date(maxDate)))); + + if (balanceUnconfirmed != 0) { + result.append("\nawaiting confirmation..."); + } + } else if (balanceUnconfirmed == 0) { + result.append("None"); + } else if (balanceUnconfirmed != 0) { + result.append("awaiting confirmation..."); + } + } else { + result.append("-- -- --"); + } + return result.toString(); + } catch (Exception e) { + return "?"; + } + } + + public int getCountOutputs() { + int count = 0; + for (int i = 0; i < mHistoryTransactions.size(); ++i) { + if (mHistoryTransactions.get(i).isInput) + continue; + ++count; + } + return count; + } + + public String getOutputsDescription() { + if (hasUnspentInfo() && hasHistoryInfo() && mHistoryTransactions.size() - mUnspentTransactions.size() > 0) { + return String.valueOf(mHistoryTransactions.size() - mUnspentTransactions.size()); + } else if (hasUnspentInfo() && hasHistoryInfo()) { + return "None"; + } else { + return "-- -- --"; + } + } + + static int UNIX_TIME_2017_01_01 = 1483228800; + + public String getLastInputDescription() { + + if (hasUnspentInfo()) { + if (mUnspentTransactions.size() > 0) { + + long maxUDT = 0; + int UnspentTransactionsAmount = 0; + + for (int i = 0; i < mUnspentTransactions.size(); ++i) { + UnspentTransactionsAmount += mUnspentTransactions.get(i).Amount; + long unix_time = GetTimestamp(mUnspentTransactions.get(i).Height); + + if (unix_time < UNIX_TIME_2017_01_01) { + return "-- -- --"; + } + unix_time = unix_time * 1000L; + if (unix_time > maxUDT) maxUDT = unix_time; + } + + Calendar cd = Calendar.getInstance(); + cd.setTimeInMillis(maxUDT); + + StringBuilder sb = new StringBuilder(); + //sb.append(String.format("%02d/%02d/%04d %02d:%02d:%02d", cd.get(Calendar.DAY_OF_MONTH), cd.get(Calendar.MONTH) + 1, cd.get(Calendar.YEAR), cd.get(Calendar.HOUR), cd.get(Calendar.MINUTE), cd.get(Calendar.SECOND))); + sb.append(Util.formatDateTime(new Date(maxUDT))); + + if (balanceUnconfirmed != null && balanceUnconfirmed != 0) { + return "awaiting confirmation..."; + } + return sb.toString(); + + } else { + if (balanceUnconfirmed != null) { + if (balanceUnconfirmed != 0) { + return "awaiting confirmation..."; + } else { + return "None"; + } + } + } + } + return "-- -- --"; + + } + + public String getLastOutputDescription() { + if (hasUnspentInfo() && hasHistoryInfo()) { + if (mHistoryTransactions.size() - mUnspentTransactions.size() > 0) { + long maxUDT = 0; + + for (int i = 0; i < mHistoryTransactions.size(); ++i) { + if (!mHistoryTransactions.get(i).isInput) + continue; + if (indexfOfUnspentTransaction(mHistoryTransactions.get(i).txID) != -1) + continue; + long unix_time = GetTimestamp(mHistoryTransactions.get(i).Height); + if (unix_time == 1296688602L) + unix_time = 0; + if (unix_time < UNIX_TIME_2017_01_01) + return "-- -- --"; + unix_time = unix_time * 1000L; + if (unix_time > maxUDT) maxUDT = unix_time; + } + + if (maxUDT == 0) { + return "None"; + } else { + return Util.formatDateTime(new Date(maxUDT)); + } + + } else { + return "None"; + } + } else { + return "-- -- --"; + } + } + + private int indexfOfUnspentTransaction(String txID) { + if (hasUnspentInfo()) { + for (int i = 0; i < mUnspentTransactions.size(); i++) { + if (mUnspentTransactions.get(i).txID.equals(txID)) return i; + } + } + return -1; + } + + public String getLastSignedDescription() { + Date dt = LastSignStorage.getLastSignDate(getWallet()); + if (dt != null) { + return Util.formatDateTime(dt); + + } else { + return "Not supported"; + } + } + + public String getPIN() { + return PIN; + } + + public void setPIN(String PIN) { + this.PIN = PIN; + } + + public void clearInfo() { + balanceConfirmed = null; + balanceUnconfirmed = null; + mUnspentTransactions = null; + mHistoryTransactions = null; + } + + public Date getPersonalizationDateTime() { + return dtPersonalization; + } + + public void setPersonalizationDateTime(Date dtPersonalization) { + this.dtPersonalization = dtPersonalization; + } + + public String getPersonalizationDateTimeDescription() { + return Util.formatDate(dtPersonalization); + + } + + private int health = 0; + + public void setHealth(int health) { + if (health > this.health) this.health = health; + } + + public boolean isHealthOK() { + return health == 0; + } + + private Issuer issuer = Issuer.Unknown; + + public void setIssuer(Issuer issuer) { + this.issuer = issuer; + } + + String contractAddress = ""; + + public void setContractAddress(String address) { + contractAddress = address; + } + + public String getContractAddress() { + return contractAddress; + } + + String tokenSymbol = ""; + + public void setTokenSymbol(String symbol) { + tokenSymbol = symbol; + } + + public String getTokenSymbol() { + return tokenSymbol; + } + + public boolean isToken() { + return !Strings.isNullOrEmpty(tokenSymbol); + } + + int tokensDecimal = 18; + + public void setTokensDecimal(int tokensDecimal) { + this.tokensDecimal = tokensDecimal; + } + + public int getTokensDecimal() { + return tokensDecimal; + } + + + public Issuer getIssuer() { + return issuer; + } + + public String getIssuerDescription() { + return issuer.getOfficialName(); + } + + byte[] issuerData; + byte[] issuerDataSignature; + + public byte[] getIssuerData() { + return issuerData; + } + + public byte[] getIssuerDataSignature() { + return issuerDataSignature; + } + + public void setIssuerData(byte[] value, byte[] signature) { + issuerData = value; + } + + boolean needWriteIssuerData = false; + + public boolean getNeedWriteIssuerData() { + return needWriteIssuerData; + } + + public void setNeedWriteIssuerData(boolean value) { + needWriteIssuerData = value; + } + + public String getIssuerDataDescription() { + return ""; + } + + + private int pauseBeforePIN2 = 0; + + public void setPauseBeforePIN2(int value) { + this.pauseBeforePIN2 = value; + } + + public int getPauseBeforePIN2() { + return pauseBeforePIN2; + } + + private Integer settingsMask = null; + + public void setSettingsMask(int settingsMask) { + this.settingsMask = settingsMask; + } + + public Boolean isReusable() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.IsReusable) != 0; + } + + public Boolean allowSwapPIN() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.AllowSwapPIN) != 0; + } + + public Boolean allowSwapPIN2() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.AllowSwapPIN2) != 0; + } + + public Boolean needCVC() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.UseCVC) != 0; + } + + public Boolean useDefaultPIN1() { + return PINStorage.isDefaultPIN(getPIN()); + } + + public Boolean useSmartSecurityDelay() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.SmartSecurityDelay) != 0; } + + public enum PIN2_Mode {Unchecked, DefaultPIN2, CustomPIN2} + + public PIN2_Mode PIN2 = PIN2_Mode.Unchecked; + + public Boolean useDefaultPIN2() { + if (PIN2 == PIN2_Mode.DefaultPIN2 || (PIN2 == PIN2_Mode.Unchecked && (needCVC() || (getPauseBeforePIN2() > 0)))) { + // define that we use default PIN2 if we try it or not try and security delay or CVC is used + return true; + } else { + return false; + } + } + + public void setUseDefaultPIN2(Boolean value) { + if (value != null) { + PIN2 = value ? PIN2_Mode.DefaultPIN2 : PIN2_Mode.CustomPIN2; + } else { + PIN2 = PIN2_Mode.Unchecked; + } + } + + public Boolean supportNDEF() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.UseNDEF) != 0; + } + + public Boolean supportOnlyOneCommandAtTime() { + if (settingsMask == null) return null; + return supportNDEF() && ((settingsMask.intValue() & SettingsMask.UseOneCommandAtTime) != 0); + } + + public Boolean supportDynamicNDEF() { + if (settingsMask == null) return null; + return supportNDEF() && ((settingsMask.intValue() & SettingsMask.UseDynamicNDEF) != 0); + } + + public Boolean supportBlock() { + if (settingsMask == null) return null; + return (settingsMask.intValue() & SettingsMask.UseBlock) != 0; + } + + public int getMaxSignatures() { + return maxSignatures; + } + + public void setMaxSignatures(int value) { + maxSignatures = value; + } + + private String firmwareVersion; + + public void setFirmwareVersion(String firmwareVersion) { + this.firmwareVersion = firmwareVersion; + } + + public String getFirmwareVersion() { + return firmwareVersion; + } + + public Boolean useDevelopersFirmware() { + return getFirmwareVersion().endsWith("d") || getFirmwareVersion().endsWith("SDK"); + } + + public static String getFirmwareVersionNumber(String version) throws Exception { + if (version == null || version.length() < 4) { + throw new Exception("Firmware version has unsupported format!"); + } + if (version.endsWith("d SDK")) { + return version.substring(0, version.length() - 5); + } else if (version.endsWith("r")) { + return version.substring(0, version.length() - 1); + } else { + return version; + } + } + + public static int[] getFirmwareVersionNumbers(String version) throws Exception { + String fwNumber = getFirmwareVersionNumber(version); + String[] strNumbers = fwNumber.split("\\."); + + if (strNumbers.length != 2) throw new Exception("Firmware version has unsupported format!"); + try { + int major = Integer.parseInt(strNumbers[0]), minor = Integer.parseInt(strNumbers[1]); + return new int[]{major, minor}; + } catch (NumberFormatException e) { + e.printStackTrace(); + throw new Exception("Firmware version has unsupported format!"); + } + } + + public Boolean isFirmwareOlder(String version) throws Exception { + int[] numbers1 = getFirmwareVersionNumbers(firmwareVersion), numbers2 = getFirmwareVersionNumbers(version); + return numbers1[0] < numbers2[0] || (numbers1[0] == numbers2[0] && numbers1[1] < numbers2[1]); + } + + public Boolean isFirmwareNewer(String version) throws Exception { + int[] numbers1 = getFirmwareVersionNumbers(firmwareVersion), numbers2 = getFirmwareVersionNumbers(version); + return numbers1[0] > numbers2[0] || (numbers1[0] == numbers2[0] && numbers1[1] > numbers2[1]); + } + + private String validationNodeDescription = ""; + + public String getValidationNodeDescription() { + return validationNodeDescription; + } + + public void setValidationNodeDescription(String validationNodeDescription) { + this.validationNodeDescription = validationNodeDescription; + } + + public enum SigningMethod { + Sign_Hash(0, "Sign hash"), + Sign_Raw(1, "Sign raw tx"), + Sign_Hash_Validated_By_Issuer(2, "Sign hash validated by issuer"), + Sign_Raw_Validated_By_Issuer(3, "Sign raw tx validated by issuer"); + + int ID; + String mDescription; + + SigningMethod(int ID, String description) { + this.ID = ID; + mDescription = description; + } + + static SigningMethod FindByID(int ID) { + SigningMethod[] methods = values(); + for (SigningMethod m : methods) { + if (m.ID == ID) return m; + } + return SigningMethod.Sign_Hash; + } + + public String getDescription() { + return mDescription; + } + } + + private SigningMethod signingMethod; + + public void setSigningMethod(int signingMethodID) { + this.signingMethod = SigningMethod.FindByID(signingMethodID); + } + + public SigningMethod getSigningMethod() { + return signingMethod; + } + + public static class HeaderInfo { + public HeaderInfo() { + + } + + public HeaderInfo(Integer height, Integer timestamp) { + Height = height; + TmStamp = timestamp; + } + + public Integer TmStamp; + public Integer Height; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putInt("TmStamp", TmStamp); + B.putInt("Height", Height); + return B; + } + + public void LoadFromBundle(Bundle B) { + TmStamp = B.getInt("TmStamp"); + Height = B.getInt("Height"); + } + } + + public static class UnspentTransaction { + public String txID; + public Integer Amount; + public Integer Height; + public String Raw = ""; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putString("txID", txID); + B.putInt("Amount", Amount); + B.putInt("Height", Height); + B.putString("Raw", Raw); + return B; + } + + public void LoadFromBundle(Bundle B) { + txID = B.getString("txID"); + Amount = B.getInt("Amount"); + Height = B.getInt("Height"); + Raw = B.getString("Raw"); + + } + } + + public static int CountOurTx(List listHTx) { + int count = 0; + for (HistoryTransaction tx : listHTx) { + + if (tx.Raw == null) + continue; + + String raw = tx.Raw; + //Log.e("H", tx.txID + " - ?"); + try { + ArrayList prevHashes = BTCUtils.getPrevTX(raw); + + boolean isOur = false; + for (byte[] hash : prevHashes) { + String checkID = BTCUtils.toHex(hash); + + for (HistoryTransaction txForCheck : listHTx) { + String id = txForCheck.txID; + //Log.e("H:", checkID + " - " + id); + if (id.equals(checkID)) { + isOur = true; + //Log.e("H", tx.txID + " - Complete"); + break; + } + } + if (isOur) { + ++count; + break; + } + } + + tx.isInput = !isOur; + } catch (BitcoinException e) { + e.printStackTrace(); + } + } + return count; + } + + public static class HistoryTransaction { + public String txID; + public Integer Height; + public String Raw = ""; + public boolean isInput = false; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putString("txID", txID); + B.putInt("Height", Height); + B.putString("Raw", Raw); + B.putBoolean("Input", isInput); + return B; + } + + public void LoadFromBundle(Bundle B) { + txID = B.getString("txID"); + Height = B.getInt("Height"); + Raw = B.getString("Raw"); + isInput = B.getBoolean("Input"); + } + } + + private List mUnspentTransactions = null; + private List mHistoryTransactions = null; + private List mHeaders = null; + + public List getUnspentTransactions() { + if (mUnspentTransactions == null) mUnspentTransactions = new ArrayList<>(); + return mUnspentTransactions; + } + + public boolean hasUnspentInfo() { + return mUnspentTransactions != null; + } + + public List getHistoryTransactions() { + if (mHistoryTransactions == null) mHistoryTransactions = new ArrayList<>(); + return mHistoryTransactions; + } + + public List getOutputTransactions() { + if (mHistoryTransactions == null) mHistoryTransactions = new ArrayList<>(); + return mHistoryTransactions; + } + + public boolean hasHistoryInfo() { + return mHistoryTransactions != null; + } + + public List getHaedersInfo() { + if (mHeaders == null) mHeaders = new ArrayList<>(); + return mHeaders; + } + + public void UpdateHeaderInfo(HeaderInfo info) { + boolean newItem = true; + for (int i = 0; i < mHeaders.size(); ++i) { + if (mHeaders.get(i).Height == info.Height) { + newItem = false; + break; + } + } + + if (newItem) { + mHeaders.add(info); + } + } + + public int GetTimestamp(int height) { + if(mHeaders == null) + return 0; + for (int i = 0; i < mHeaders.size(); ++i) { + if (mHeaders.get(i).Height == height) { + return mHeaders.get(i).TmStamp; + } + } + + return 0; + } + + public boolean hasHeaderInfo() { + return mHeaders != null; + } + + public Tangem_Card(String UID) { + this.UID = UID; + } + + public void setStatus(Status status) { + this.status = status; + } + + public Status getStatus() { + return status; + } + + public byte[] getCID() { + return CID; + } + + public void setCID(byte[] value) { + this.CID = value; + } + + public String getCIDDescription() { + String strCID = Util.bytesToHex(CID); + try { + return strCID.substring(0, 4) + " " + strCID.substring(4, 8) + " " + strCID.substring(8, 12) + " " + strCID.substring(12, 16); + } catch (Exception e) { + return strCID; + } + } + + public void setWallet(String wallet) { + this.wallet = wallet; + } + + public String getWallet() { + return wallet; + } + + public String getShortWalletString() { + if (wallet.length() < 22) { + return wallet; + } else { + return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length()); + } + } + + public String getUID() { + return UID; + } + + public void setUID(String UID) { + this.UID = UID; + } + + public Long getBalance() { + return balanceConfirmed + balanceUnconfirmed; + } + + public Long getBalanceUnconfirmed() { + return balanceUnconfirmed; + } + + public void setBalanceConfirmed(Long balance) { + this.balanceConfirmed = balance; + } + + public void setBalanceUnconfirmed(Long balance) { + this.balanceUnconfirmed = balance; + } + + public boolean hasBalanceInfo() { + return balanceConfirmed != null && balanceUnconfirmed != null; + } + + public String getDecimalBalance() { + return balanceDecimal; + } + + public String getDecimalBalanceAlter() { + return balanceDecimalAlter; + } + + public void setDecimalBalance(String value) { + balanceDecimal = value; + } + + public void setDecimalBalanceAlter(String value) { + balanceDecimalAlter = value; + } + + private byte[] offlineBalance; + + public void setOfflineBalance(byte[] offlineBalance) { + this.offlineBalance = offlineBalance; + } + + public byte[] getOfflineBalance() { + return offlineBalance; + } + + public void clearOfflineBalance() { + offlineBalance = null; + } + + public void setError(String error) { + this.error = error; + } + + public String getError() { + return error; + } + + public void setMessage(String value) { + this.message = value; + } + + public String getMessage() { + return message; + } + + public enum Status { + NotPersonalized(0), Empty(1), Loaded(2), Purged(3); + + Status(int Code) { + mCode = Code; + } + + private int mCode; + + public int getCode() { + return mCode; + } + + public static Status fromCode(int code) { + for (Status s : values()) { + if (s.getCode() == code) return s; + } + return null; + } + } + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + SaveToBundle(B); + return B; + } + + public void SaveToBundle(Bundle B) { + B.putString("UID", UID); + B.putByteArray("CID", CID); + B.putString("PIN", PIN); + B.putString("PIN2", PIN2.name()); + B.putString("Status", status.name()); + B.putString("Blockchain", blockchainID); + B.putString("BlockchainName", blochchainName); + B.putInt("TokensDecimal", tokensDecimal); + B.putString("TokenSymbol", tokenSymbol); + B.putString("ContractAddress", contractAddress); + B.putString("validationNodeDescription", validationNodeDescription); + if (dtPersonalization != null) B.putLong("dtPersonalization", dtPersonalization.getTime()); + B.putInt("RemainingSignatures", remainingSignatures); + B.putInt("MaxSignatures", maxSignatures); + B.putInt("Health", health); + if (settingsMask != null) B.putInt("settingsMask", settingsMask); + B.putInt("pauseBeforePIN2", pauseBeforePIN2); + if (signingMethod != null) B.putString("signingMethod", signingMethod.name()); + if (manufacturer != null) B.putString("Manufacturer", manufacturer.name()); + if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name()); + if (issuer != null) B.putString("Issuer", issuer.name()); + if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion); + B.putString("BalanceDecimal", balanceDecimal); + B.putString("BalanceDecimalAlter", balanceDecimalAlter); + B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed); + B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid()); + B.putByteArray("CardPublicKey", getCardPublicKey()); + + B.putString("Wallet", wallet); + B.putString("Error", error); + B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid()); + if (pbWalletKey != null) + B.putByteArray("PublicKey", pbWalletKey); + if (pbWalletKeyRar != null) + B.putByteArray("PublicKeyRar", pbWalletKeyRar); + if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed); + if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed); + + if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance()); + + if (getIssuerData() != null && getIssuerDataSignature() != null) { + B.putByteArray("IssuerData", getIssuerData()); + B.putByteArray("IssuerDataSignature", getIssuerDataSignature()); + B.putBoolean("NeedWriteIssuerData", getNeedWriteIssuerData()); + } + + if (mUnspentTransactions != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < mUnspentTransactions.size(); i++) { + BB.putBundle(i.toString(), mUnspentTransactions.get(i).getAsBundle()); + } + B.putBundle("UnspentTransactions", BB); + } + if (mHeaders != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < mHeaders.size(); i++) { + BB.putBundle(i.toString(), mHeaders.get(i).getAsBundle()); + } + B.putBundle("Headers", BB); + } + if (mHistoryTransactions != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < mHistoryTransactions.size(); i++) { + BB.putBundle(i.toString(), mHistoryTransactions.get(i).getAsBundle()); + } + B.putBundle("HistoryTransactions", BB); + } + B.putFloat("rate", rate); + B.putFloat("rateAlter", rateAlter); + B.putString("confirmTx", GetConfirmTXCount().toString(16)); + } + + public void LoadFromBundle(Bundle B) { + UID = B.getString("UID"); + CID = B.getByteArray("CID"); + PIN = B.getString("PIN"); + PIN2 = PIN2_Mode.valueOf(B.getString("PIN2")); + status = Status.valueOf(B.getString("Status")); + wallet = B.getString("Wallet"); + error = B.getString("Error"); + blockchainID = B.getString("Blockchain"); + tokensDecimal = B.getInt("TokensDecimal", 18); + tokenSymbol = B.getString("TokenSymbol", ""); + contractAddress = B.getString("ContractAddress", ""); + if (B.containsKey("BlockchainName")) + blochchainName = B.getString("BlockchainName", ""); + validationNodeDescription = B.getString("validationNodeDescription"); + if (B.containsKey("dtPersonalization")) { + dtPersonalization = new Date(B.getLong("dtPersonalization")); + } + balanceDecimal = B.getString("BalanceDecimal"); + balanceDecimalAlter = B.getString("BalanceDecimalAlter"); + remainingSignatures = B.getInt("RemainingSignatures"); + maxSignatures = B.getInt("MaxSignatures"); + health = B.getInt("health"); + if (B.containsKey("settingsMask")) settingsMask = B.getInt("settingsMask"); + pauseBeforePIN2 = B.getInt("pauseBeforePIN2"); + if (B.containsKey("signingMethod")) + signingMethod = SigningMethod.valueOf(B.getString("signingMethod")); + if (B.containsKey("Manufacturer")) + manufacturer = Manufacturer.valueOf(B.getString("Manufacturer")); + manufacturerConfirmed = B.getBoolean("ManufacturerConfirmed"); + if (B.containsKey("EncryptionMode")) + encryptionMode = EncryptionMode.valueOf(B.getString("EncryptionMode")); + else + encryptionMode = null; + + if (B.containsKey("Issuer")) issuer = Issuer.valueOf(B.getString("Issuer")); + if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion"); + + cardPublicKeyValid = B.getBoolean("CardPublicKeyValid"); + if (B.containsKey("CardPublicKey")) setCardPublicKey(B.getByteArray("CardPublicKey")); + + if (B.containsKey("BalanceConfirmed")) balanceConfirmed = B.getLong("BalanceConfirmed"); + else balanceConfirmed = null; + if (B.containsKey("BalanceUnconfirmed")) + balanceUnconfirmed = B.getLong("BalanceUnconfirmed"); + else balanceUnconfirmed = null; + + if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance")); + else clearOfflineBalance(); + + if (B.containsKey("IssuerData")) + setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature")); + else setIssuerData(null, null); + + if (B.containsKey("NeedWriteIssuerData")) + setNeedWriteIssuerData(B.getBoolean("NeedWriteIssuerData")); + + walletPublicKeyValid = B.getBoolean("WalletPublicKeyValid"); + if (B.containsKey("PublicKey")) { + pbWalletKey = B.getByteArray("PublicKey"); + } + if (B.containsKey("PublicKeyRar")) { + pbWalletKeyRar = B.getByteArray("PublicKeyRar"); + } + + if (B.containsKey("UnspentTransactions")) { + mUnspentTransactions = new ArrayList<>(); + Bundle BB = B.getBundle("UnspentTransactions"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + UnspentTransaction t = new UnspentTransaction(); + t.LoadFromBundle(BB.getBundle(i.toString())); + mUnspentTransactions.add(t); + i++; + } + } + if (B.containsKey("Headers")) { + mHeaders = new ArrayList<>(); + Bundle BB = B.getBundle("Headers"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + HeaderInfo t = new HeaderInfo(); + t.LoadFromBundle(BB.getBundle(i.toString())); + mHeaders.add(t); + i++; + } + } + if (B.containsKey("HistoryTransactions")) { + mHistoryTransactions = new ArrayList<>(); + Bundle BB = B.getBundle("HistoryTransactions"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + HistoryTransaction t = new HistoryTransaction(); + t.LoadFromBundle(BB.getBundle(i.toString())); + mHistoryTransactions.add(t); + i++; + } + } + if (B.containsKey("rate")) + rate = B.getFloat("rate"); + if (B.containsKey("rateAlter")) + rateAlter = B.getFloat("rateAlter"); + if (B.containsKey("confirmTx")) + countConfirmTX = new BigInteger(B.getString("confirmTx"), 16); + } + + public enum EncryptionMode { + None((byte) 0x0), Fast((byte) 0x1), Strong((byte) 0x2); + + private byte P; + + EncryptionMode(byte P) { + this.P = P; + } + + public int getP() { + return P; + } + } + + public EncryptionMode encryptionMode = EncryptionMode.None; +} diff --git a/app/src/main/java/com/tangem/wallet/TokenEngine.java b/app/src/main/java/com/tangem/wallet/TokenEngine.java new file mode 100644 index 0000000000..d9975bd2db --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/TokenEngine.java @@ -0,0 +1,436 @@ +package com.tangem.wallet; + +import android.net.Uri; +import android.util.Log; + +import com.google.common.base.Strings; +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.TLV; + +import org.bitcoinj.core.ECKey; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Date; + +import static com.tangem.wallet.FormatUtil.GetDecimalFormat; + +/** + * Created by Ilia on 20.03.2018. + */ + +public class TokenEngine extends CoinEngine{ + public String GetNextNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + public int GetNextNodePort(Tangem_Card mCard) + { + return 60001; + } + public String GetNode(Tangem_Card mCard) + { + return "abc1.hsmiths.com"; + } + public int GetNodePort(Tangem_Card mCard) + { + return 60001; + } + public void SwitchNode(Tangem_Card mCard) + { + } + public boolean AwaitingConfirmation(Tangem_Card card) + { + return false; + } + + public boolean InOutPutVisible() + { + return false; + } + + public String GetBalanceCurrency(Tangem_Card card) + { + String currency = card.getTokenSymbol(); + if(Strings.isNullOrEmpty(currency)) + return "NoN"; + return currency; + } + + public String GetFeeCurrency() + { + return "Gwei"; + } + + BigDecimal convertToEth(String value) + { + BigInteger m = new BigInteger(value, 10); + BigDecimal n = new BigDecimal(m); + BigDecimal d = n.divide(new BigDecimal("1000000000000000000")); + d = d.setScale(8, RoundingMode.DOWN); + return d; + } + + + public int GetTokenDecimals(Tangem_Card card) + { + return card.getTokensDecimal(); + } + + public String GetContractAddress(Tangem_Card card) + { + return card.getContractAddress(); + } + public boolean IsNeedCheckNode() + { + return false; + } + + public boolean ValdateAddress(String address, Tangem_Card card) { + if (address == null || address.isEmpty()) { + return false; + } + + if(!address.startsWith("0x")&&!address.startsWith("0X")) + { + return false; + } + + if(address.length()!=42) + { + return false; + } + + return true; + } + + public String GetBalanceAlterValue(Tangem_Card mCard) + { + String dec = mCard.getDecimalBalanceAlter(); + BigDecimal d = convertToEth(dec); + String s = d.toString(); + + String pattern = "#0.000"; // If you like 4 zeros + DecimalFormat myFormatter = new DecimalFormat(pattern); + String output = myFormatter.format(d); + return output; + } + + public String GetBalanceValue(Tangem_Card mCard) + { + if(!HasBalanceInfo(mCard)) + return "-- -- -- " + GetBalanceCurrency(mCard); + + String dec = mCard.getDecimalBalance(); + BigDecimal d = new BigDecimal(dec); + BigDecimal p = new BigDecimal(10); + p = p.pow(GetTokenDecimals(mCard)); + BigDecimal l = d.divide(p); + + String pattern = "#0.000"; // If you like 4 zeros + DecimalFormat myFormatter = new DecimalFormat(pattern); + String output = myFormatter.format(l); + return output; + } + + public boolean CheckAmount(Tangem_Card card, String amount) throws Exception + { + DecimalFormat decimalFormat = GetDecimalFormat(); + BigDecimal amountValue = (BigDecimal) decimalFormat.parse(amount); //new BigDecimal(strAmount); + BigDecimal maxValue = new BigDecimal(GetBalanceValue(card)); + if(amountValue.compareTo(maxValue) > 0 ) + { + return false; + } + + return true; + } + + public Long GetBalanceLong(Tangem_Card mCard) + { + return mCard.getBalance(); + } + + public boolean IsBalanceAlterNotZero(Tangem_Card card) + { + String balance = card.getDecimalBalanceAlter(); + if(balance == null || balance == "") + return false; + + BigDecimal bi = new BigDecimal(balance); + + if (BigDecimal.ZERO.compareTo(bi) == 0) + return false; + + return true; + } + + public boolean IsBalanceNotZero(Tangem_Card card) + { + String balance = card.getDecimalBalance(); + if(balance == null || balance == "") + return false; + + BigDecimal bi = new BigDecimal(balance); + + if (BigDecimal.ZERO.compareTo(bi) == 0) + return false; + + return true; + } + + public boolean HasBalanceInfo(Tangem_Card card) + { + String balance = card.getDecimalBalance(); + if(balance == null || balance == "") + return false; + + String balanceEx = card.getDecimalBalanceAlter(); + if(balanceEx == null || balanceEx == "") + return false; + return true; + } + + @Override + public String GetBalanceEquivalent(Tangem_Card mCard) { + if(!HasBalanceInfo(mCard)){ + return "-- -- -- "; + } + String dec = mCard.getDecimalBalance(); + BigDecimal d = convertToEth(dec); + return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + @Override + public String GetBalance(Tangem_Card mCard) { + if(!HasBalanceInfo(mCard)){ + return "-- -- -- " + GetBalanceCurrency(mCard); + } + + String output = GetBalanceValue(mCard); + String s = output + " " + GetBalanceCurrency(mCard); + return s; + } + + + + + public String GetBalanceWithAlter(Tangem_Card mCard) + { + //return GetBalance(mCard) + "\n(" + GetBalanceAlterValue(mCard) + " ETH)"; + return " " + GetBalance(mCard) + "
+ " + GetBalanceAlterValue(mCard) + " ETH for gas"; + } + + public String calculateAddress(Tangem_Card mCard, byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + Keccak256 kec = new Keccak256(); + int lenPk = pkUncompressed.length; + if (lenPk < 2) { + throw new IllegalArgumentException("Uncompress public key length is invald"); + } + byte[] cleanKey = new byte[lenPk - 1]; + for (int i = 0; i < cleanKey.length; ++i) { + cleanKey[i] = pkUncompressed[i + 1]; + } + byte[] r = kec.digest(cleanKey); + + byte[] address = new byte[20]; + for (int i = 0; i < 20; ++i) { + address[i] = r[i + 12]; + } + + return String.format("0x%s", BTCUtils.toHex(address)); + } + + @Override + public String ConvertByteArrayToAmount(Tangem_Card mCard, byte[] bytes) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public byte[] ConvertAmountToByteArray(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + @Override + public String GetAmountDescription(Tangem_Card mCard, String amount) throws Exception { + throw new Exception("Not implemented"); + } + + + public String GetAmountEqualentDescriptor(Tangem_Card mCard, String value) + { + BigDecimal d = new BigDecimal(value); + return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRate()); + } + + public String GetFeeEqualentDescriptor(Tangem_Card mCard, String value) + { + BigDecimal d = new BigDecimal(value); + return EthEngine.getAmountEquivalentDescriptionETH(d, mCard.getRateAlter()); + } + + public Uri getShareWalletURIExplorer(Tangem_Card mCard) + { + return Uri.parse("https://etherscan.io/token/"+GetContractAddress(mCard)+"?a=" + mCard.getWallet()); + } + + public Uri getShareWalletURI(Tangem_Card mCard) + { + return Uri.parse("" + mCard.getWallet()); + } + + public boolean CheckUnspentTransaction(Tangem_Card mCard) + { + return true; + } + + public boolean CheckAmountValie(Tangem_Card mCard, String amountValue, String feeValue, Long minFeeInInternalUnits) + { + Long fee = null; + BigDecimal amount = null; + try { + amount = new BigDecimal(GetBalanceAlterValue(mCard));//mCard.InternalUnitsFromString(amountValue); + fee = mCard.InternalUnitsFromString(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if(fee == null || amount == null) + return false; + + if(fee == 0 || amount.compareTo(BigDecimal.ZERO) == 0) + return false; + + + if(fee < minFeeInInternalUnits) + return false; + + + BigDecimal tmpFee = new BigDecimal(feeValue); + BigDecimal tmpAmount = amount; + tmpAmount = tmpAmount.multiply(new BigDecimal("1000000000")); + + if (tmpFee.compareTo(tmpAmount) > 0) + return false; + + return true; + } + + public String EvaluteFeeEquivalent(Tangem_Card mCard, String fee) + { + BigDecimal gweFee = new BigDecimal(fee); + gweFee = gweFee.divide(new BigDecimal("1000000000")); + gweFee = gweFee.setScale(18, RoundingMode.DOWN); + return GetFeeEqualentDescriptor(mCard, gweFee.toString()); + } + + public byte[] Sign(String feeValue, String amountValue, String toValue, Tangem_Card mCard, CardProtocol protocol) throws Exception { + + BigInteger nonceValue = mCard.GetConfirmTXCount(); + byte[] pbKey = mCard.getWalletPublicKey(); + boolean flag = (mCard.getSigningMethod()== Tangem_Card.SigningMethod.Sign_Hash_Validated_By_Issuer); + Issuer issuer = mCard.getIssuer(); + + + BigInteger fee = new BigInteger(feeValue, 10); + + BigDecimal amountDecValue = new BigDecimal(amountValue); + + int d = GetTokenDecimals(mCard); + BigDecimal amountDec = new BigDecimal("10"); + amountDec = amountDec.pow(d); + amountDec = amountDecValue.multiply(amountDec); + + //amountDec = amountDec.multiply(new BigDecimal("1000000000")); + + + BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10); + + + + + //amount = amount.subtract(fee); + + BigInteger nonce = nonceValue; + BigInteger gasPrice = fee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(60000); + Integer chainId = ETH_Transaction.ChainEnum.Mainnet.getValue(); + BigInteger amountZero = BigInteger.ZERO; + + Long multiplicator = 1000000000L; + + gasPrice = gasPrice.multiply(BigInteger.valueOf(multiplicator)); + + String to = toValue; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + String contractAddress = GetContractAddress(mCard); + + if (contractAddress.startsWith("0x") || contractAddress.startsWith("0X")) { + contractAddress = contractAddress.substring(2); + } + + String amountLeadZero = amount.toString(16); + if (amountLeadZero.startsWith("0x") || amountLeadZero.startsWith("0X")) { + amountLeadZero = amountLeadZero.substring(2); + } + + while(amountLeadZero.length() < 64) + { + amountLeadZero = "0" + amountLeadZero; + } + + String cmd = "a9059cbb000000000000000000000000"+to+amountLeadZero; //TODO only for BAT + + + byte[] data = BTCUtils.fromHex(cmd); + ETH_Transaction tx = ETH_Transaction.create(contractAddress, amountZero, nonce, gasPrice, gasLimit, chainId, data); + + byte[][] hashesForSign = new byte[1][]; + byte[] for_hash = tx.getRawHash(); + hashesForSign[0] = for_hash; + + byte[] signFromCard = null; + try { + signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value; + // TODO slice signFromCard to hashes.length parts + } catch (Exception ex) { + Log.e("ETH", ex.getMessage()); + return null; + } + + LastSignStorage.setLastSignDate(mCard.getWallet(), new Date()); + + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if(!f) + { + Log.e("ETH-CHECK", "Sign Failed."); + } + + tx.signature = new ECDSASignature_ETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e("ETH", "invalid v"); + return null; + } + tx.signature.v = (byte) v; + Log.e("ETH_v", String.valueOf(v)); + + byte[] realTX = tx.getEncoded(); + return realTX; + } +} diff --git a/app/src/main/java/com/tangem/wallet/Transaction.java b/app/src/main/java/com/tangem/wallet/Transaction.java new file mode 100644 index 0000000000..3c755b03c0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/Transaction.java @@ -0,0 +1,632 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +import org.spongycastle.jcajce.provider.symmetric.ARC4; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Stack; + +@SuppressWarnings("WeakerAccess") +public final class Transaction { + public final int version; + public final Input[] inputs; + public final Output[] outputs; + public final int lockTime; + + public Transaction(byte[] rawBytes) throws BitcoinException { + if (rawBytes == null) { + throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "empty input"); + } + BitcoinInputStream bais = null; + try { + bais = new BitcoinInputStream(rawBytes); + version = bais.readInt32(); + if (version != 1 && version != 2 && version != 3) { + throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unsupported TX version", version); + } + + + int inputsCount = 0; + int first = bais.readByte(); + if(first == 0) + { + int skip = bais.readByte(); + inputsCount = bais.readByte(); + } + else + { + inputsCount = first; + } + //int inputsCount = (int) bais.readVarInt(); TODO: + inputs = new Input[inputsCount]; + for (int i = 0; i < inputsCount; i++) { + OutPoint outPoint = new OutPoint(BTCUtils.reverse(bais.readChars(32)), bais.readInt32()); + byte[] script = bais.readChars((int) bais.readVarInt()); + int sequence = bais.readInt32(); + inputs[i] = new Input(outPoint, new Script(script), sequence); + } + int outputsCount = (int) bais.readVarInt(); + outputs = new Output[outputsCount]; + for (int i = 0; i < outputsCount; i++) { + long value = bais.readInt64(); + long scriptSize = bais.readVarInt(); + if (scriptSize < 0 || scriptSize > 10_000_000) { + throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "Script size for output " + i + + " is strange (" + scriptSize + " bytes)."); + } + byte[] script = bais.readChars((int) scriptSize); + outputs[i] = new Output(value, new Script(script)); + } + lockTime = bais.readInt32(); + } catch (EOFException e) { + throw new BitcoinException(BitcoinException.ERR_BAD_FORMAT, "TX incomplete"); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to read TX"); + } catch (Error e) { + throw new IllegalArgumentException("Unable to read TX: " + e); + } finally { + if (bais != null) { + try { + bais.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public Transaction(Input[] inputs, Output[] outputs, int lockTime) { + this.version = 1; + this.inputs = inputs; + this.outputs = outputs; + this.lockTime = lockTime; + } + + public byte[] getBytes() { + BitcoinOutputStream baos = new BitcoinOutputStream(); + try { + baos.writeInt32(version); + baos.writeVarInt(inputs.length); + for (Input input : inputs) { + baos.write(BTCUtils.reverse(input.outPoint.hash)); + baos.writeInt32(input.outPoint.index); + int scriptLen = input.script == null ? 0 : input.script.bytes.length; + baos.writeVarInt(scriptLen); + if (scriptLen > 0) { + baos.write(input.script.bytes); + } + baos.writeInt32(input.sequence); + } + baos.writeVarInt(outputs.length); + for (Output output : outputs) { + baos.writeInt64(output.value); + int scriptLen = output.script == null ? 0 : output.script.bytes.length; + baos.writeVarInt(scriptLen); + if (scriptLen > 0) { + baos.write(output.script.bytes); + } + } + baos.writeInt32(lockTime); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + baos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return baos.toByteArray(); + + } + + @Override + public String toString() { + return "{" + + "\n\"inputs\":\n" + printAsJsonArray(inputs) + + ",\n\"outputs\":\n" + printAsJsonArray(outputs) + + ",\n\"lockTime\":\"" + lockTime + "\"}\n"; + } + + private String printAsJsonArray(Object[] a) { + if (a == null) { + return "null"; + } + if (a.length == 0) { + return "[]"; + } + int iMax = a.length - 1; + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (int i = 0; ; i++) { + sb.append(String.valueOf(a[i])); + if (i == iMax) + return sb.append(']').toString(); + sb.append(",\n"); + } + } + + public static class Input { + public final OutPoint outPoint; + public final Script script; + public final int sequence; + + public Input(OutPoint outPoint, Script script, int sequence) { + this.outPoint = outPoint; + this.script = script; + this.sequence = sequence; + } + + @Override + public String toString() { + return "{\n\"outPoint\":" + outPoint + ",\n\"script\":\"" + script + "\",\n\"sequence\":\"" + Integer.toHexString(sequence) + "\"\n}\n"; + } + } + + public static class OutPoint { + public final byte[] hash;//32-byte hash of the transaction from which we want to redeem an output + public final int index;//Four-byte field denoting the output index we want to redeem from the transaction with the above hash (output number 2 = output index 1) + + public OutPoint(byte[] hash, int index) { + this.hash = hash; + this.index = index; + } + + @Override + public String toString() { + return "{" + "\"hash\":\"" + BTCUtils.toHex(hash) + "\", \"index\":\"" + index + "\"}"; + } + } + + public static class Output { + public final long value; + public final Script script; + + public Output(long value, Script script) { + this.value = value; + this.script = script; + } + + @Override + public String toString() { + return "{\n\"value\":\"" + value * 1e-8 + "\",\"script\":\"" + script + "\"\n}"; + } + } + + public static final class Script { + + public static class ScriptInvalidException extends Exception { + public ScriptInvalidException() { + } + + public ScriptInvalidException(String s) { + super(s); + } + } + + public static final byte OP_FALSE = 0; + public static final byte OP_TRUE = 0x51; + public static final byte OP_PUSHDATA1 = 0x4c; + public static final byte OP_PUSHDATA2 = 0x4d; + public static final byte OP_PUSHDATA4 = 0x4e; + public static final byte OP_DUP = 0x76;//Duplicates the top stack item. + public static final byte OP_DROP = 0x75; + public static final byte OP_HASH160 = (byte) 0xA9;//The input is hashed twice: first with SHA-256 and then with RIPEMD-160. + public static final byte OP_VERIFY = 0x69;//Marks transaction as invalid if top stack value is not true. True is removed, but false is not. + public static final byte OP_EQUAL = (byte) 0x87;//Returns 1 if the inputs are exactly equal, 0 otherwise. + public static final byte OP_EQUALVERIFY = (byte) 0x88;//Same as OP_EQUAL, but runs OP_VERIFY afterward. + public static final byte OP_CHECKSIG = (byte) 0xAC;//The entire transaction's outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise. + public static final byte OP_CHECKSIGVERIFY = (byte) 0xAD; + public static final byte OP_NOP = 0x61; + + public static final byte SIGHASH_ALL = 1; + + public final byte[] bytes; + + public Script(byte[] rawBytes) { + bytes = rawBytes; + } + + public Script(byte[] data1, byte[] data2) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(data1.length + data2.length + 2); + try { + writeBytes(data1, baos); + writeBytes(data2, baos); + baos.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + bytes = baos.toByteArray(); + } + + private static void writeBytes(byte[] data, ByteArrayOutputStream baos) throws IOException { + if (data.length < OP_PUSHDATA1) { + baos.write(data.length); + } else if (data.length < 0xff) { + baos.write(OP_PUSHDATA1); + baos.write(data.length); + } else if (data.length < 0xffff) { + baos.write(OP_PUSHDATA2); + baos.write(data.length & 0xff); + baos.write((data.length >> 8) & 0xff); + } else { + baos.write(OP_PUSHDATA4); + baos.write(data.length & 0xff); + baos.write((data.length >> 8) & 0xff); + baos.write((data.length >> 16) & 0xff); + baos.write((data.length >>> 24) & 0xff); + } + baos.write(data); + } + + public void run(Stack stack) throws ScriptInvalidException { + run(0, null, stack); + } + + public void run(int inputIndex, Transaction tx, Stack 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 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); + } + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java b/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java new file mode 100644 index 0000000000..a4462b69d6 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/UnspentOutputInfo.java @@ -0,0 +1,28 @@ +package com.tangem.wallet; + +/** + * Created by Ilia on 29.09.2017. + */ + +@SuppressWarnings("WeakerAccess") +public class UnspentOutputInfo { + public final byte[] txHash; + public final Transaction.Script script; + public final long value; + public final int outputIndex; + public final long confirmations; + public String txHashForBuild; + public byte[] scriptForBuild; + public byte[] bodyDoubleHash; + public byte[] bodyHash; + + public UnspentOutputInfo(byte[] txHash, Transaction.Script script, long value, int outputIndex, long confirmations, String hashForBuild, byte[] sign) { + this.txHash = txHash; + this.script = script; + this.value = value; + this.outputIndex = outputIndex; + this.confirmations = confirmations; + this.txHashForBuild = hashForBuild; + this.scriptForBuild = sign; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/VerifyCardActivity.java b/app/src/main/java/com/tangem/wallet/VerifyCardActivity.java new file mode 100644 index 0000000000..30a8153057 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerifyCardActivity.java @@ -0,0 +1,28 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + + +public class VerifyCardActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_verify_card); + + MainActivity.commonInit(getApplicationContext()); + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + VerifyCardActivityFragment verifyCardActivityFragment= (VerifyCardActivityFragment) getSupportFragmentManager().findFragmentById(R.id.verify_card_fragment); + Intent data= verifyCardActivityFragment.prepareResultIntent(); + data.putExtra("modification", "update"); + setResult(Activity.RESULT_OK, data); + finish(); + } +} diff --git a/app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java b/app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java new file mode 100644 index 0000000000..53f64d5fa1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerifyCardActivityFragment.java @@ -0,0 +1,360 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.tangem.cardReader.NfcManager; + +import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; + +public class VerifyCardActivityFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener, NfcAdapter.ReaderCallback { + + Tangem_Card mCard; + TextView tvCardID, tvManufacturer, tvRegistrationDate, tvCardIdentity, tvLastSigned, tvRemainingSignatures, tvReusable, tvOk, tvError, tvMessage, + tvIssuer, tvIssuerData, tvFeatures, tvBlockchain, tvSignedTx, tvSigningMethod, tvFirmware, tvWalletIdentity, tvWallet; + ImageView ivBlockchain, ivPIN, ivPIN2orSecurityDelay, ivDeveloperVersion; + SwipeRefreshLayout mSwipeRefreshLayout; + private NfcManager mNfcManager; + + public VerifyCardActivityFragment() { + + } + + public void onRefresh() { + mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_verify_card, container, false); + + mNfcManager = new NfcManager(this.getActivity(), this); + + + // SwipeRefreshLayout + mSwipeRefreshLayout = v.findViewById(R.id.swipe_container); + mSwipeRefreshLayout.setOnRefreshListener(this); + + mCard = new Tangem_Card(getActivity().getIntent().getStringExtra("UID")); + mCard.LoadFromBundle(getActivity().getIntent().getExtras().getBundle("Card")); + tvCardID = v.findViewById(R.id.tvCardID); + + tvLastSigned = v.findViewById(R.id.tvLastSigned); + tvRemainingSignatures = v.findViewById(R.id.tvRemainingSignatures); + + tvReusable = v.findViewById(R.id.tvReusable); + + tvManufacturer = v.findViewById(R.id.tvManufacturerInfo); + + tvCardIdentity = v.findViewById(R.id.tvCardIdentity); + + tvRegistrationDate = v.findViewById(R.id.tvCardRegistredDate); + + ivBlockchain = v.findViewById(R.id.imgBlockchain); + ivPIN = v.findViewById(R.id.imgPIN); + ivPIN2orSecurityDelay = v.findViewById(R.id.imgPIN2orSecurityDelay); + ivDeveloperVersion = v.findViewById(R.id.imgDeveloperVersion); + + tvError = v.findViewById(R.id.tvError); + tvMessage = v.findViewById(R.id.tvMessage); + + tvIssuer = v.findViewById(R.id.tvIssuer); + tvIssuerData = v.findViewById(R.id.tvIssuerData); + + tvFirmware = v.findViewById(R.id.tvFirmware); + tvFeatures = v.findViewById(R.id.tvFeatures); + tvBlockchain = v.findViewById(R.id.tvBlockchain); + + tvSignedTx = v.findViewById(R.id.tvSignedTx); + tvSigningMethod = v.findViewById(R.id.tvSigningMethod); + + tvOk = v.findViewById(R.id.tvOk); + if (tvOk != null) { + tvOk.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent data = prepareResultIntent(); + data.putExtra("modification", "update"); + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); + } + }); + } + + tvWallet = v.findViewById(R.id.tvWallet); + tvWalletIdentity = v.findViewById(R.id.tvWalletIdentity); + + UpdateViews(); + +// if (NeedUpdate) { +// mSwipeRefreshLayout.setRefreshing(true); +// mSwipeRefreshLayout.postDelayed(new Runnable() { +// @Override +// public void run() { +// onRefresh(); +// } +// }, 1000); +// } + return v; + } + + void UpdateViews() { + try { + if (timerHideErrorAndMessage != null) { + timerHideErrorAndMessage.cancel(); + timerHideErrorAndMessage = null; + } + tvCardID.setText(mCard.getCIDDescription()); + + if (mCard.getError() == null || mCard.getError().isEmpty()) { + tvError.setVisibility(View.GONE); + tvError.setText(""); + } else { + tvError.setVisibility(View.VISIBLE); + tvError.setText(mCard.getError()); + } + if (mCard.getMessage() == null || mCard.getMessage().isEmpty()) { + tvMessage.setVisibility(View.GONE); + tvMessage.setText(""); + } else { + tvMessage.setVisibility(View.VISIBLE); + tvMessage.setText(mCard.getMessage()); + } + + tvManufacturer.setText(mCard.getManufacturer().getOfficialName()); + + if (mCard.isManufacturerConfirmed() && mCard.isCardPublicKeyValid()) { + tvCardIdentity.setText("Attested"); + tvCardIdentity.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + } else { + tvCardIdentity.setText("Not confirmed"); + tvCardIdentity.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + } + + tvIssuer.setText(mCard.getIssuerDescription()); + tvIssuerData.setText(mCard.getIssuerDataDescription()); + + tvRegistrationDate.setText(mCard.getPersonalizationDateTimeDescription()); + + //tvBlockchain.setText(mCard.getBlockchain().getOfficialName()); + tvBlockchain.setText(mCard.getBlockchainName()); + ivBlockchain.setImageResource(mCard.getBlockchain().getImageResource(this.getContext(), mCard.getTokenSymbol())); + + if (mCard.isReusable()) { + tvReusable.setText("Reusable"); + } else { + tvReusable.setText("One-off banknote"); + } + + tvSigningMethod.setText(mCard.getSigningMethod().getDescription()); + + if (mCard.getStatus() == Tangem_Card.Status.Loaded || mCard.getStatus() == Tangem_Card.Status.Purged) { + + tvLastSigned.setText(mCard.getLastSignedDescription()); + if (mCard.getRemainingSignatures() == 0) { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText("None"); + } else if (mCard.getRemainingSignatures() == 1) { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText("Last one!"); + } else if (mCard.getRemainingSignatures() > 1000) { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText("Unlimited"); + } else { + tvRemainingSignatures.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + tvRemainingSignatures.setText(String.valueOf(mCard.getRemainingSignatures())); + } + tvSignedTx.setText(String.valueOf(mCard.getMaxSignatures() - mCard.getRemainingSignatures())); + } else { + tvLastSigned.setText(""); + tvRemainingSignatures.setText(""); + tvSignedTx.setText(""); + } + + tvFirmware.setText(mCard.getFirmwareVersion()); + + String features = ""; + + if (mCard.allowSwapPIN() && mCard.allowSwapPIN2()) { + features += "Allows change PIN1 and PIN2\n"; + } else if (mCard.allowSwapPIN()) { + features += "Allows change PIN1\n"; + } else if (mCard.allowSwapPIN2()) { + features += "Allows change PIN2\n"; + } else { + features += "Fixed PIN1 and PIN2\n"; + } + + if (mCard.needCVC()) { + features += "Requires CVC\n"; + } + + if (mCard.supportDynamicNDEF()) { + features += "Dynamic NDEF for iOS\n"; + } else if (mCard.supportNDEF()) { + features += "NDEF\n"; + } + + if (mCard.supportBlock()) { + features += "Blockable\n"; + } + + if (mCard.supportOnlyOneCommandAtTime()) { + features += "Atomic command mode"; + } + + if (features.endsWith("\n")) { + features = features.substring(0, features.length() - 1); + } + tvFeatures.setText(features); + + if (mCard.useDefaultPIN1()) { + ivPIN.setImageResource(R.drawable.unlock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN.setImageResource(R.drawable.lock_pin1); + ivPIN.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN1 code", Toast.LENGTH_LONG).show(); + } + }); + } + + if (mCard.getPauseBeforePIN2() > 0 && (mCard.useDefaultPIN2() || !mCard.useSmartSecurityDelay())) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.timer); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), String.format("This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code", mCard.getPauseBeforePIN2() / 1000.0), Toast.LENGTH_LONG).show(); + } + }); + + } else if (mCard.useDefaultPIN2()) { + ivPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by default PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2); + ivPIN2orSecurityDelay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "This banknote is protected by user's PIN2 code", Toast.LENGTH_LONG).show(); + } + }); + } + + + if (mCard.useDevelopersFirmware()) { + ivDeveloperVersion.setImageResource(R.drawable.ic_developer_version); + ivDeveloperVersion.setVisibility(View.VISIBLE); + ivDeveloperVersion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(getContext(), "Unlocked banknote, only for development use", Toast.LENGTH_LONG).show(); + } + }); + } else { + ivDeveloperVersion.setVisibility(View.INVISIBLE); + } + + if (mCard.getStatus() == Tangem_Card.Status.Loaded) { + tvWallet.setText(mCard.getShortWalletString()); + if (mCard.isWalletPublicKeyValid()) { + tvWalletIdentity.setText("Possession proved"); + tvWalletIdentity.setTextColor(getResources().getColor(R.color.confirmed, getActivity().getTheme())); + } else { + tvWalletIdentity.setText("Possession NOT proved"); + tvWalletIdentity.setTextColor(getResources().getColor(R.color.not_confirmed, getActivity().getTheme())); + } + } else { + tvWallet.setText("not available"); + tvWalletIdentity.setText("-- -- --"); + } + + timerHideErrorAndMessage = new Timer(); + timerHideErrorAndMessage.schedule(new TimerTask() { + @Override + public void run() { + tvError.post(new Runnable() { + @Override + public void run() { + tvMessage.setVisibility(View.GONE); + tvError.setVisibility(View.GONE); + mCard.setError(null); + mCard.setMessage(null); + } + }); + } + }, 5000); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + Timer timerHideErrorAndMessage = null; + + public Intent prepareResultIntent() { + Intent data = new Intent(); + data.putExtra("UID", mCard.getUID()); + data.putExtra("Card", mCard.getAsBundle()); + return data; + } + + + @Override + public void onResume() { + super.onResume(); + mNfcManager.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mNfcManager.onPause(); + } + + @Override + public void onStop() { + super.onStop(); + mNfcManager.onStop(); + } + + @Override + public void onTagDiscovered(Tag tag) { + try { + Log.w(getClass().getName(), "Ignore discovered tag!"); + mNfcManager.IgnoreTag(tag); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/VerifyCardTask.java b/app/src/main/java/com/tangem/wallet/VerifyCardTask.java new file mode 100644 index 0000000000..bc0b7cf576 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerifyCardTask.java @@ -0,0 +1,109 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.nfc.tech.IsoDep; +import android.util.Log; + +import com.tangem.cardReader.CardProtocol; +import com.tangem.cardReader.NfcManager; + +/** + * Created by dvol on 04.02.2018. + */ + +public class VerifyCardTask extends Thread { + + IsoDep mIsoDep; + CardProtocol.Notifications mNotifications; + private final String logTag = "VerifyCardTask"; + private boolean isCancelled = false; + private Context mContext; + private Tangem_Card mCard; + private NfcManager mNfcManager; + + VerifyCardTask(Context context, Tangem_Card card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) { + mCard = card; + mContext = context; + mIsoDep = isoDep; + mNotifications = notifications; + mNfcManager = nfcManager; + } + + @Override + public void run() { + if (mIsoDep == null) { + return; + } + try { + // for Samsung's bugs - + // Workaround for the Samsung Galaxy S5 (since the + // first connection always hangs on transceive). + int timeout = mIsoDep.getTimeout(); + mIsoDep.connect(); + mIsoDep.close(); + mIsoDep.connect(); + mIsoDep.setTimeout(timeout); + try { + CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications); + mNotifications.OnReadStart(protocol); + try { + mNotifications.OnReadProgress(protocol, 5); + + Log.i("VerifyCardTask", "[-- Start verify card --]"); + + if (isCancelled) return; + + String PIN = mCard.getPIN(); + protocol.setPIN(PIN); + protocol.run_Read(); + PINStorage.setLastUsedPIN(PIN); + mNotifications.OnReadProgress(protocol, 30); + if (isCancelled) return; + protocol.run_VerifyCard(); + mNotifications.OnReadProgress(protocol, 60); + Log.i("VerifyCardTask", "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); + if (isCancelled) return; + if (protocol.getCard().getStatus() == Tangem_Card.Status.Loaded) { + protocol.run_CheckWalletWithSignatureVerify(); + mNotifications.OnReadProgress(protocol, 90); + } + + + +// if (isCancelled) return; +// if (protocol.getCard().getStatus() == Tangem_Card.Status.Loaded) { +// protocol.run_CheckWithSignatureVerify(); +// } + + } catch (Exception e) { + e.printStackTrace(); + protocol.setError(e); + + } finally { + Log.i("VerifyCardTask", "[-- Finish verify card --]"); + mNotifications.OnReadFinish(protocol); + } + } finally { + mNfcManager.IgnoreTag(mIsoDep.getTag()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void cancel(Boolean AllowInterrupt) { + try { + if (this.isAlive()) { + isCancelled = true; + join(500); + } + if (this.isAlive() && AllowInterrupt) { + interrupt(); + mNotifications.OnReadCancel(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/tangem/wallet/VerticalTextView.java b/app/src/main/java/com/tangem/wallet/VerticalTextView.java new file mode 100644 index 0000000000..53bbecdb9a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/VerticalTextView.java @@ -0,0 +1,70 @@ +package com.tangem.wallet; + +import android.content.Context; +import android.graphics.Canvas; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.TextView; + +public class VerticalTextView extends TextView +{ + final boolean topDown; + + public VerticalTextView( Context context, + AttributeSet attrs ) + { + super( context, attrs ); + final int gravity = getGravity(); + if ( Gravity.isVertical( gravity ) + && ( gravity & Gravity.VERTICAL_GRAVITY_MASK ) + == Gravity.BOTTOM ) + { + setGravity( + ( gravity & Gravity.HORIZONTAL_GRAVITY_MASK ) + | Gravity.TOP ); + topDown = false; + } + else + { + topDown = true; + } + } + + @Override + protected void onMeasure( int widthMeasureSpec, + int heightMeasureSpec ) + { + super.onMeasure( heightMeasureSpec, + widthMeasureSpec ); + setMeasuredDimension( getMeasuredHeight(), + getMeasuredWidth() ); + } + + @Override + protected void onDraw( Canvas canvas ) + { + TextPaint textPaint = getPaint(); + textPaint.setColor( getCurrentTextColor() ); + textPaint.drawableState = getDrawableState(); + + canvas.save(); + + if ( topDown ) + { + canvas.translate( getWidth(), 0 ); + canvas.rotate( 90 ); + } + else + { + canvas.translate( 0, getHeight() ); + canvas.rotate( -90 ); + } + + canvas.translate( getCompoundPaddingLeft(), + getExtendedPaddingTop() ); + + getLayout().draw( canvas ); + canvas.restore(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java b/app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java new file mode 100644 index 0000000000..df1c2ac71e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/WaitSecurityDelayDialog.java @@ -0,0 +1,166 @@ +package com.tangem.wallet; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ProgressBar; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Created by dvol on 06.03.2018. + */ +public class WaitSecurityDelayDialog extends DialogFragment { + ProgressBar progressBar; + int msTimeout = 60000, msProgress = 0; + Timer timer; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + LayoutInflater inflater = getActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + View v = inflater.inflate(R.layout.dialog_wait_pin2, null); + + progressBar = v.findViewById(R.id.progressBar); + progressBar.setMax(msTimeout); + progressBar.setProgress(msProgress); + + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + progressBar.post(new Runnable() { + @Override + public void run() { + int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); + if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) { + WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000); + } + } + }); + } + }, 1000, 1000); + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.tangem_logo_small_new) + .setTitle("Security delay") + .setView(v) + .setCancelable(false) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + } + + public void setup(int msTimeout, int msProgress) { + this.msTimeout = msTimeout; + this.msProgress = msProgress; + } + + public void setRemainingTimeout(final int msec) { + progressBar.post(new Runnable() { + @Override + public void run() { + int progress = WaitSecurityDelayDialog.this.progressBar.getProgress(); + if (timer != null) { + // we get delay latency from card for first time - don't change progress by timer, only by card answer + progressBar.setMax(progress + msec); + timer.cancel(); + timer = null; + } else { + int newProgress = progressBar.getMax() - msec; + if (newProgress > progress) { + progressBar.setProgress(newProgress); + } else { + progressBar.setMax(progress + msec); + } + } + } + }); + } + + static Timer timerToShowDelayDialog = null; + static WaitSecurityDelayDialog instance = null; + + public static WaitSecurityDelayDialog getInstance() { + if (instance == null) { + instance = new WaitSecurityDelayDialog(); + } + return instance; + } + + private final static int MinRemainingDelayToShowDialog=1000; + private final static int DelayBeforeShowDialog=5000; + + public static void onReadBeforeRequest(final Activity activity, final int timeout) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog+MinRemainingDelayToShowDialog) return; + timerToShowDelayDialog = new Timer(); + timerToShowDelayDialog.schedule(new TimerTask() { + @Override + public void run() { + if (WaitSecurityDelayDialog.instance != null) return; + instance = new WaitSecurityDelayDialog(); + instance.setup(timeout, DelayBeforeShowDialog); + instance.setCancelable(false); + instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog"); + } + }, DelayBeforeShowDialog); + } + }); + } + + public static void onReadAfterRequest(final Activity activity) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog == null) return; + timerToShowDelayDialog.cancel(); + timerToShowDelayDialog = null; + } + }); + } + + public static void OnReadWait(final Activity activity, final int msec) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (timerToShowDelayDialog != null) { + timerToShowDelayDialog.cancel(); + timerToShowDelayDialog = null; + } + + if (msec == 0) { + if (instance != null) { + instance.dismiss(); + instance = null; + } + return; + } + if (instance == null) { + if( msec>MinRemainingDelayToShowDialog ) { + instance = new WaitSecurityDelayDialog(); + // 1000ms - card delay notification interval + instance.setup(msec + 1000, 1000); + instance.setCancelable(false); + instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog"); + } + } else { + instance.setRemainingTimeout(msec); + } + } + }); + } +} diff --git a/app/src/main/java/com/tangem/wallet/WalletInfoFragment.java b/app/src/main/java/com/tangem/wallet/WalletInfoFragment.java new file mode 100644 index 0000000000..3978fee305 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/WalletInfoFragment.java @@ -0,0 +1,151 @@ +package com.tangem.wallet; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import java.util.Hashtable; + +import static android.content.Context.CLIPBOARD_SERVICE; + +/** + * A simple {@link Fragment} subclass. + * Activities that contain this fragment must implement the + * {@link OnFragmentInteractionListener} interface + * to handle interaction events. + * Use the {@link WalletInfoFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class WalletInfoFragment extends Fragment { + + // TODO: Rename and change types of parameters + private Tangem_Card mCard; + + private OnFragmentInteractionListener mListener; + + public WalletInfoFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @return A new instance of fragment WalletInfoFragment. + */ + // TODO: Rename and change types and number of parameters + public static WalletInfoFragment newInstance(Tangem_Card card) { + WalletInfoFragment fragment = new WalletInfoFragment(); + Bundle args = new Bundle(); + args.putString("UID",card.getUID()); + card.SaveToBundle(args); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mCard = new Tangem_Card(getArguments().getString("UID")); + mCard.LoadFromBundle(getArguments()); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View result=inflater.inflate(R.layout.fragment_wallet_info, container, false); + + ImageView mImage= (ImageView)result.findViewById(R.id.qrWallet); + try { + mImage.setImageBitmap(generateQrCode(mCard.getWallet())); + } catch (WriterException e) { + e.printStackTrace(); + } + TextView mText=(TextView)result.findViewById(R.id.strWallet); + mText.setText(mCard.getWallet()); + + mText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + TextView mText = (TextView) view; + ClipboardManager clipboard = (ClipboardManager)getActivity().getSystemService(CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(mText.getText(), mText.getText())); + Toast.makeText(getContext(),"Copied to clipboard",Toast.LENGTH_LONG).show(); + } + }); + return result; + } + + public static Bitmap generateQrCode(String myCodeText) throws WriterException { + Hashtable hintMap = new Hashtable(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // H = 30% damage + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + + int size = 256; + + BitMatrix bitMatrix = qrCodeWriter.encode(myCodeText, BarcodeFormat.QR_CODE, size, size, hintMap); + int width = bitMatrix.getWidth(); + Bitmap bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565); + for (int x = 0; x < width; x++) { + for (int y = 0; y < width; y++) { + bmp.setPixel(y, x, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE); + } + } + return bmp; + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnFragmentInteractionListener) { + mListener = (OnFragmentInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + *

+ * See the Android Training lesson Communicating with Other Fragments for more information. + */ + public interface OnFragmentInteractionListener { + // TODO: Update argument type and name +// void onFragmentInteraction(Uri uri); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_show_menu.png b/app/src/main/res/drawable-hdpi/ic_show_menu.png new file mode 100644 index 0000000000..e7b9bd3ec0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_show_menu.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_show_menu.png b/app/src/main/res/drawable-mdpi/ic_show_menu.png new file mode 100644 index 0000000000..615265ea03 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_show_menu.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..c7bd21dbd8 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/ic_show_menu.png b/app/src/main/res/drawable-xhdpi/ic_show_menu.png new file mode 100644 index 0000000000..efd69d739f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_show_menu.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_show_menu.png b/app/src/main/res/drawable-xxhdpi/ic_show_menu.png new file mode 100644 index 0000000000..82061f9137 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_show_menu.png differ diff --git a/app/src/main/res/drawable/bat_token.png b/app/src/main/res/drawable/bat_token.png new file mode 100644 index 0000000000..ef5b5917a5 Binary files /dev/null and b/app/src/main/res/drawable/bat_token.png differ diff --git a/app/src/main/res/drawable/bitcoin_cash.png b/app/src/main/res/drawable/bitcoin_cash.png new file mode 100644 index 0000000000..473a23b336 Binary files /dev/null and b/app/src/main/res/drawable/bitcoin_cash.png differ diff --git a/app/src/main/res/drawable/bitcoins.png b/app/src/main/res/drawable/bitcoins.png new file mode 100644 index 0000000000..6d37edc11e Binary files /dev/null and b/app/src/main/res/drawable/bitcoins.png differ diff --git a/app/src/main/res/drawable/bitcoins_testnet.png b/app/src/main/res/drawable/bitcoins_testnet.png new file mode 100644 index 0000000000..8c91d1baad Binary files /dev/null and b/app/src/main/res/drawable/bitcoins_testnet.png differ diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 0000000000..1707c3802a --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/card_bg.png b/app/src/main/res/drawable/card_bg.png new file mode 100644 index 0000000000..72b4041941 Binary files /dev/null and b/app/src/main/res/drawable/card_bg.png differ diff --git a/app/src/main/res/drawable/chain_lookup.png b/app/src/main/res/drawable/chain_lookup.png new file mode 100644 index 0000000000..8cabad3eb2 Binary files /dev/null and b/app/src/main/res/drawable/chain_lookup.png differ diff --git a/app/src/main/res/drawable/coinmarketcap.png b/app/src/main/res/drawable/coinmarketcap.png new file mode 100644 index 0000000000..75f0d24e67 Binary files /dev/null and b/app/src/main/res/drawable/coinmarketcap.png differ diff --git a/app/src/main/res/drawable/ert_token.png b/app/src/main/res/drawable/ert_token.png new file mode 100644 index 0000000000..adbe18e652 Binary files /dev/null and b/app/src/main/res/drawable/ert_token.png differ diff --git a/app/src/main/res/drawable/ethereum.png b/app/src/main/res/drawable/ethereum.png new file mode 100644 index 0000000000..4da06a795b Binary files /dev/null and b/app/src/main/res/drawable/ethereum.png differ diff --git a/app/src/main/res/drawable/ethereum2.png b/app/src/main/res/drawable/ethereum2.png new file mode 100644 index 0000000000..62400e8975 Binary files /dev/null and b/app/src/main/res/drawable/ethereum2.png differ diff --git a/app/src/main/res/drawable/ethereum_testnet.png b/app/src/main/res/drawable/ethereum_testnet.png new file mode 100644 index 0000000000..be8fe3238c Binary files /dev/null and b/app/src/main/res/drawable/ethereum_testnet.png differ diff --git a/app/src/main/res/drawable/hand_card.png b/app/src/main/res/drawable/hand_card.png new file mode 100644 index 0000000000..08032733f9 Binary files /dev/null and b/app/src/main/res/drawable/hand_card.png differ diff --git a/app/src/main/res/drawable/ic_action_nfc_gray.png b/app/src/main/res/drawable/ic_action_nfc_gray.png new file mode 100644 index 0000000000..22837067bf Binary files /dev/null and b/app/src/main/res/drawable/ic_action_nfc_gray.png differ diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000000..c872f16707 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_developer_version.png b/app/src/main/res/drawable/ic_developer_version.png new file mode 100644 index 0000000000..072e1479c5 Binary files /dev/null and b/app/src/main/res/drawable/ic_developer_version.png differ diff --git a/app/src/main/res/drawable/ic_fp_40px.png b/app/src/main/res/drawable/ic_fp_40px.png new file mode 100644 index 0000000000..122f44257b Binary files /dev/null and b/app/src/main/res/drawable/ic_fp_40px.png differ diff --git a/app/src/main/res/drawable/ic_info.png b/app/src/main/res/drawable/ic_info.png new file mode 100644 index 0000000000..042ee75397 Binary files /dev/null and b/app/src/main/res/drawable/ic_info.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..d5fccc538c --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..7522eb07a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_local_atm_black_24dp.xml b/app/src/main/res/drawable/ic_local_atm_black_24dp.xml new file mode 100644 index 0000000000..1b86138060 --- /dev/null +++ b/app/src/main/res/drawable/ic_local_atm_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo_small.png b/app/src/main/res/drawable/ic_logo_small.png new file mode 100644 index 0000000000..1bedd8792a Binary files /dev/null and b/app/src/main/res/drawable/ic_logo_small.png differ diff --git a/app/src/main/res/drawable/ic_search_gray.png b/app/src/main/res/drawable/ic_search_gray.png new file mode 100644 index 0000000000..88be1d6fc4 Binary files /dev/null and b/app/src/main/res/drawable/ic_search_gray.png differ diff --git a/app/src/main/res/drawable/ic_show_menu.png b/app/src/main/res/drawable/ic_show_menu.png new file mode 100644 index 0000000000..b4bd987bcb Binary files /dev/null and b/app/src/main/res/drawable/ic_show_menu.png differ diff --git a/app/src/main/res/drawable/icon_oreo.png b/app/src/main/res/drawable/icon_oreo.png new file mode 100644 index 0000000000..0cdf09d1e1 Binary files /dev/null and b/app/src/main/res/drawable/icon_oreo.png differ diff --git a/app/src/main/res/drawable/lock_pin.png b/app/src/main/res/drawable/lock_pin.png new file mode 100644 index 0000000000..af1a616a4d Binary files /dev/null and b/app/src/main/res/drawable/lock_pin.png differ diff --git a/app/src/main/res/drawable/lock_pin1.png b/app/src/main/res/drawable/lock_pin1.png new file mode 100644 index 0000000000..49045881fd Binary files /dev/null and b/app/src/main/res/drawable/lock_pin1.png differ diff --git a/app/src/main/res/drawable/lock_pin2.png b/app/src/main/res/drawable/lock_pin2.png new file mode 100644 index 0000000000..4befee5537 Binary files /dev/null and b/app/src/main/res/drawable/lock_pin2.png differ diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000000..35f101bb12 Binary files /dev/null and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/drawable/nfc_scan.png b/app/src/main/res/drawable/nfc_scan.png new file mode 100644 index 0000000000..9968bb221b Binary files /dev/null and b/app/src/main/res/drawable/nfc_scan.png differ diff --git a/app/src/main/res/drawable/parsed_msg_bg.xml b/app/src/main/res/drawable/parsed_msg_bg.xml new file mode 100644 index 0000000000..ad2e4a821e --- /dev/null +++ b/app/src/main/res/drawable/parsed_msg_bg.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/parsed_msg_bg_pressed.xml b/app/src/main/res/drawable/parsed_msg_bg_pressed.xml new file mode 100644 index 0000000000..47b21d4368 --- /dev/null +++ b/app/src/main/res/drawable/parsed_msg_bg_pressed.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/parsed_msg_bg_states.xml b/app/src/main/res/drawable/parsed_msg_bg_states.xml new file mode 100644 index 0000000000..91aff98446 --- /dev/null +++ b/app/src/main/res/drawable/parsed_msg_bg_states.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/qr_scan.png b/app/src/main/res/drawable/qr_scan.png new file mode 100644 index 0000000000..9755d27edf Binary files /dev/null and b/app/src/main/res/drawable/qr_scan.png differ diff --git a/app/src/main/res/drawable/rfid.gif b/app/src/main/res/drawable/rfid.gif new file mode 100644 index 0000000000..9d83a79312 Binary files /dev/null and b/app/src/main/res/drawable/rfid.gif differ diff --git a/app/src/main/res/drawable/smartphone.png b/app/src/main/res/drawable/smartphone.png new file mode 100644 index 0000000000..e637727034 Binary files /dev/null and b/app/src/main/res/drawable/smartphone.png differ diff --git a/app/src/main/res/drawable/tangem.png b/app/src/main/res/drawable/tangem.png new file mode 100644 index 0000000000..b72fbf6e1e Binary files /dev/null and b/app/src/main/res/drawable/tangem.png differ diff --git a/app/src/main/res/drawable/tangem2.png b/app/src/main/res/drawable/tangem2.png new file mode 100644 index 0000000000..63e1321485 Binary files /dev/null and b/app/src/main/res/drawable/tangem2.png differ diff --git a/app/src/main/res/drawable/tangem_logo_full_new.png b/app/src/main/res/drawable/tangem_logo_full_new.png new file mode 100644 index 0000000000..ffd6d294ea Binary files /dev/null and b/app/src/main/res/drawable/tangem_logo_full_new.png differ diff --git a/app/src/main/res/drawable/tangem_logo_icon_new.png b/app/src/main/res/drawable/tangem_logo_icon_new.png new file mode 100644 index 0000000000..63e1321485 Binary files /dev/null and b/app/src/main/res/drawable/tangem_logo_icon_new.png differ diff --git a/app/src/main/res/drawable/tangem_logo_small_new.png b/app/src/main/res/drawable/tangem_logo_small_new.png new file mode 100644 index 0000000000..e3d59e92b7 Binary files /dev/null and b/app/src/main/res/drawable/tangem_logo_small_new.png differ diff --git a/app/src/main/res/drawable/tap.png b/app/src/main/res/drawable/tap.png new file mode 100644 index 0000000000..f6d967c34a Binary files /dev/null and b/app/src/main/res/drawable/tap.png differ diff --git a/app/src/main/res/drawable/timer.png b/app/src/main/res/drawable/timer.png new file mode 100644 index 0000000000..a971c810b7 Binary files /dev/null and b/app/src/main/res/drawable/timer.png differ diff --git a/app/src/main/res/drawable/unlock_pin.png b/app/src/main/res/drawable/unlock_pin.png new file mode 100644 index 0000000000..642a5cba9c Binary files /dev/null and b/app/src/main/res/drawable/unlock_pin.png differ diff --git a/app/src/main/res/drawable/unlock_pin1.png b/app/src/main/res/drawable/unlock_pin1.png new file mode 100644 index 0000000000..44320d1656 Binary files /dev/null and b/app/src/main/res/drawable/unlock_pin1.png differ diff --git a/app/src/main/res/drawable/unlock_pin2.png b/app/src/main/res/drawable/unlock_pin2.png new file mode 100644 index 0000000000..9e3be6f0d3 Binary files /dev/null and b/app/src/main/res/drawable/unlock_pin2.png differ diff --git a/app/src/main/res/drawable/unnamed.png b/app/src/main/res/drawable/unnamed.png new file mode 100644 index 0000000000..7bba27850d Binary files /dev/null and b/app/src/main/res/drawable/unnamed.png differ diff --git a/app/src/main/res/font/montserrat_light.otf b/app/src/main/res/font/montserrat_light.otf new file mode 100644 index 0000000000..f2f0e2df08 Binary files /dev/null and b/app/src/main/res/font/montserrat_light.otf differ diff --git a/app/src/main/res/font/raleway_el.ttf b/app/src/main/res/font/raleway_el.ttf new file mode 100644 index 0000000000..7611e96c32 Binary files /dev/null and b/app/src/main/res/font/raleway_el.ttf differ diff --git a/app/src/main/res/font/raleway_r.ttf b/app/src/main/res/font/raleway_r.ttf new file mode 100644 index 0000000000..e570a2d5c3 Binary files /dev/null and b/app/src/main/res/font/raleway_r.ttf differ diff --git a/app/src/main/res/layout/activity_card_info.xml b/app/src/main/res/layout/activity_card_info.xml new file mode 100644 index 0000000000..fdf12aedb8 --- /dev/null +++ b/app/src/main/res/layout/activity_card_info.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_confirm_payment.xml b/app/src/main/res/layout/activity_confirm_payment.xml new file mode 100644 index 0000000000..b32b90dd10 --- /dev/null +++ b/app/src/main/res/layout/activity_confirm_payment.xml @@ -0,0 +1,423 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +