Add walletcore and start UX overhaul

pull/785/head
James Brown 5 years ago
parent 709a9c9655
commit c99593603e
  1. 12
      app/build.gradle
  2. 2
      app/src/debug/AndroidManifest.xml
  3. 7
      app/src/main/AndroidManifest.xml
  4. 12
      app/src/main/java/io/stormbird/wallet/entity/AuthenticationCallback.java
  5. 17
      app/src/main/java/io/stormbird/wallet/entity/Wallet.java
  6. 46
      app/src/main/java/io/stormbird/wallet/interact/CreateWalletInteract.java
  7. 2
      app/src/main/java/io/stormbird/wallet/interact/FetchWalletsInteract.java
  8. 66
      app/src/main/java/io/stormbird/wallet/repository/WalletDataRealmSource.java
  9. 2
      app/src/main/java/io/stormbird/wallet/repository/WalletRepository.java
  10. 2
      app/src/main/java/io/stormbird/wallet/repository/WalletRepositoryType.java
  11. 5
      app/src/main/java/io/stormbird/wallet/repository/entity/RealmWalletData.java
  12. 400
      app/src/main/java/io/stormbird/wallet/service/HDKeyService.java
  13. 21
      app/src/main/java/io/stormbird/wallet/ui/BackupSeedPhrase.java
  14. 24
      app/src/main/java/io/stormbird/wallet/ui/NewSettingsFragment.java
  15. 2
      app/src/main/java/io/stormbird/wallet/ui/SplashActivity.java
  16. 3
      app/src/main/java/io/stormbird/wallet/ui/WalletsActivity.java
  17. 48
      app/src/main/java/io/stormbird/wallet/util/PasswordStoreFactory.java
  18. 5
      app/src/main/java/io/stormbird/wallet/viewmodel/SplashViewModel.java
  19. 6
      app/src/main/java/io/stormbird/wallet/viewmodel/WalletActionsViewModel.java
  20. 16
      app/src/main/java/io/stormbird/wallet/viewmodel/WalletsViewModel.java
  21. 6
      app/src/main/java/io/stormbird/wallet/widget/AWalletConfirmationDialog.java
  22. 120
      app/src/main/java/io/stormbird/wallet/widget/SignTransactionDialog.java
  23. BIN
      app/src/main/res/drawable-hdpi/ic_shortcut_fingerprint.png
  24. BIN
      app/src/main/res/drawable-hdpi/seed_graphic.png
  25. BIN
      app/src/main/res/drawable-mdpi/ic_shortcut_fingerprint.png
  26. BIN
      app/src/main/res/drawable-mdpi/seed_graphic.png
  27. BIN
      app/src/main/res/drawable-xhdpi/ic_shortcut_fingerprint.png
  28. BIN
      app/src/main/res/drawable-xhdpi/seed_graphic.png
  29. BIN
      app/src/main/res/drawable-xxhdpi/ic_shortcut_fingerprint.png
  30. BIN
      app/src/main/res/drawable-xxhdpi/seed_graphic.png
  31. BIN
      app/src/main/res/drawable-xxxhdpi/ic_shortcut_fingerprint.png
  32. BIN
      app/src/main/res/drawable-xxxhdpi/seed_graphic.png
  33. 2
      app/src/main/res/drawable/background_marketplace_event.xml
  34. 6
      app/src/main/res/drawable/background_warning_event.xml
  35. 5
      app/src/main/res/drawable/backup_warning_red.xml
  36. 81
      app/src/main/res/layout/activity_backup_seed.xml
  37. 12
      app/src/main/res/layout/dialog_awallet_confirmation.xml
  38. 44
      app/src/main/res/layout/fragment_settings.xml
  39. 2048
      app/src/main/res/raw/bip39_chinese
  40. 2048
      app/src/main/res/raw/bip39_english
  41. 2048
      app/src/main/res/raw/bip39_spanish
  42. 3
      app/src/main/res/values-es/strings.xml
  43. 3
      app/src/main/res/values-zh/strings.xml
  44. 3
      app/src/main/res/values/colors.xml
  45. 4
      app/src/main/res/values/strings.xml
  46. 6
      build.gradle
  47. 4
      gradle/wrapper/gradle-wrapper.properties

@ -36,7 +36,7 @@ android {
}
defaultConfig {
applicationId "io.stormbird.wallet"
minSdkVersion 18
minSdkVersion 23
targetSdkVersion 28
versionCode 59
versionName "2.01.0"
@ -80,13 +80,13 @@ android {
project.ext {
retrofitVersion = "2.5.0"
okhttpVersion = "3.14.1"
okhttpVersion = "4.0.0"
picassoVersion = "2.71828"
supportVersion = "28.0.0"
web3jVersion = "4.2.0-android"
gethVersion = "1.8.11"
gsonVersion = "2.8.5"
rxJavaVersion = "2.2.8"
rxJavaVersion = "2.2.10"
rxAndroidVersion = "2.1.1"
daggerVersion = "2.22"
}
@ -160,7 +160,11 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.trustwallet.walletcore:walletcore:0.10.0'
implementation 'com.android.support:multidex:1.0.3'
// PW
implementation fileTree(include: ['*.jar'], dir: 'libs')

@ -18,7 +18,7 @@
<!-- <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> -->
<application
android:name=".App"
android:name="io.stormbird.wallet.App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

@ -11,6 +11,9 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-feature android:name="android.hardware.fingerprint"
android:required="false"/>
<application
android:name="io.stormbird.wallet.App"
@ -220,6 +223,10 @@
<activity
android:name="io.stormbird.wallet.ui.SelectNetworkActivity"
android:label="Select Network Filters" />
<activity
android:name="io.stormbird.wallet.ui.BackupSeedPhrase"
android:label="Backup Seed Phrase" />
</application>
</manifest>

@ -0,0 +1,12 @@
package io.stormbird.wallet.entity;
/**
* Created by James on 9/06/2019.
* Stormbird in Sydney
*/
public interface AuthenticationCallback
{
void authenticatePass(int callbackId);
void authenticateFail(String fail);
}

@ -13,12 +13,14 @@ public class Wallet implements Parcelable {
public String balance;
public String ENSname;
public String name;
public WalletType type;
public Wallet(String address) {
this.address = address;
this.balance = "-";
this.ENSname = "";
this.name = "";
this.type = WalletType.NOT_DEFINED;
//this.publicKey = padLeft(Numeric.cleanHexPrefix(address.toLowerCase()), 128); //TODO: Get this from ecrecover
}
@ -27,9 +29,16 @@ public class Wallet implements Parcelable {
balance = in.readString();
ENSname = in.readString();
name = in.readString();
int t = in.readInt();
type = WalletType.values()[t];
//this.publicKey = padLeft(address, 128);
}
public void setWalletType(WalletType wType)
{
type = wType;
}
public static final Creator<Wallet> CREATOR = new Creator<Wallet>() {
@Override
public Wallet createFromParcel(Parcel in) {
@ -58,6 +67,7 @@ public class Wallet implements Parcelable {
parcel.writeString(balance);
parcel.writeString(ENSname);
parcel.writeString(name);
parcel.writeInt(type.ordinal());
}
public void setWalletBalance(BigDecimal balanceBD)
@ -66,4 +76,11 @@ public class Wallet implements Parcelable {
.setScale(4, RoundingMode.HALF_DOWN)
.stripTrailingZeros().toPlainString();
}
public enum WalletType
{
NOT_DEFINED,
KEYSTORE,
HDKEY
}
}

@ -1,15 +1,27 @@
package io.stormbird.wallet.interact;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.ArraySet;
import com.google.gson.Gson;
import io.stormbird.wallet.entity.DApp;
import io.stormbird.wallet.entity.Wallet;
import io.stormbird.wallet.interact.rx.operator.Operators;
import io.stormbird.wallet.repository.PasswordStore;
import io.stormbird.wallet.repository.WalletRepositoryType;
import io.reactivex.Single;
import io.stormbird.wallet.service.HDKeyService;
import wallet.core.jni.HDWallet;
import java.util.List;
import java.util.Set;
import static io.stormbird.wallet.interact.rx.operator.Operators.completableErrorProxy;
public class CreateWalletInteract {
public class CreateWalletInteract implements KeyStoreInterface {
private final WalletRepositoryType walletRepository;
private final PasswordStore passwordStore;
@ -19,12 +31,32 @@ public class CreateWalletInteract {
this.passwordStore = passwordStore;
}
public Single<Wallet> create() {
return passwordStore.generatePassword()
.flatMap(masterPassword -> walletRepository
.createWallet(masterPassword)
.compose(Operators.savePassword(passwordStore, walletRepository, masterPassword))
.flatMap(wallet -> passwordVerification(wallet, masterPassword)));
//TODO: handle errors, don't return until key is created
public Single<Wallet> create(Activity ctx) {
return Single.fromCallable(() -> {
HDKeyService hdService = new HDKeyService(ctx);
String addr = hdService.createNewHDKey();
Wallet wallet = new Wallet(addr);
wallet.setWalletType(Wallet.WalletType.HDKEY);
flagAsNotBackedUp(ctx, addr);
return wallet;
});
// return passwordStore.generatePassword()
// .flatMap(masterPassword -> walletRepository
// .createWallet(masterPassword)
// .compose(Operators.savePassword(passwordStore, walletRepository, masterPassword))
// .flatMap(wallet -> passwordVerification(wallet, masterPassword)));
}
public void flagAsNotBackedUp(Context context, String walletAddr) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
Set<String> notBackedUp = pref.getStringSet ("notbackedup", new ArraySet<>());
notBackedUp.add(walletAddr);
PreferenceManager
.getDefaultSharedPreferences(context)
.edit()
.putStringSet("notbackedup", notBackedUp)
.apply();
}
private Single<Wallet> passwordVerification(Wallet wallet, String masterPassword) {

@ -39,7 +39,7 @@ public class FetchWalletsInteract {
return accountRepository.storeWallets(wallets, isMainNet);
}
public Single<Integer> storeWallet(Wallet wallet) {
public Single<Wallet> storeWallet(Wallet wallet) {
return accountRepository.storeWallet(wallet);
}
}

@ -11,6 +11,7 @@ import io.reactivex.Single;
import io.realm.Realm;
import io.realm.RealmResults;
import io.stormbird.wallet.entity.Wallet;
import io.stormbird.wallet.repository.entity.RealmToken;
import io.stormbird.wallet.repository.entity.RealmWalletData;
import io.stormbird.wallet.service.RealmManager;
@ -30,24 +31,52 @@ public class WalletDataRealmSource {
public Single<Wallet[]> populateWalletData(Wallet[] wallets) {
return Single.fromCallable(() -> {
List<Wallet> walletList = new ArrayList<>();
Map<String, Wallet> wMap = new HashMap<>();
for (Wallet w : wallets) if (w.address != null) wMap.put(w.address, w);
try (Realm realm = realmManager.getWalletDataRealmInstance()) {
for (Wallet wallet : wallets)
{
RealmWalletData data = realm.where(RealmWalletData.class)
.equalTo("address", wallet.address)
.findFirst();
RealmResults<RealmWalletData> data = realm.where(RealmWalletData.class)
.findAll();
if (data != null)
for (RealmWalletData d : data)
{
if (d != null)
{
wallet.ENSname = data.getENSName();
wallet.balance = balance(data);
wallet.name = data.getName();
Wallet wallet = new Wallet(d.getAddress());
if (wMap.containsKey(d.getAddress()))
{
wallet.setWalletType(Wallet.WalletType.KEYSTORE);
}
else
{
wallet.setWalletType(Wallet.WalletType.HDKEY);
}
wallet.ENSname = d.getENSName();
wallet.balance = balance(d);
wallet.name = d.getName();
walletList.add(wallet);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return wallets;
//now add types from legacy store
for (Wallet w : wallets)
{
boolean found = false;
for (Wallet wl : walletList)
{
if (wl.address.equals(w.address)) { found = true; break; }
}
if (!found)
{
w.setWalletType(Wallet.WalletType.KEYSTORE);
walletList.add(w);
}
}
return walletList.toArray(new Wallet[0]);
});
}
@ -118,9 +147,8 @@ public class WalletDataRealmSource {
});
}
public Single<Integer> storeWallet(Wallet wallet) {
public Single<Wallet> storeWallet(Wallet wallet) {
return Single.fromCallable(() -> {
Integer updated = 0;
try (Realm realm = realmManager.getWalletDataRealmInstance()) {
realm.beginTransaction();
@ -128,19 +156,21 @@ public class WalletDataRealmSource {
.equalTo("address", wallet.address)
.findFirst();
if (realmWallet != null) {
realmWallet.setName(wallet.name);
updated++;
} else {
if (realmWallet == null) {
realmWallet = realm.createObject(RealmWalletData.class, wallet.address);
realmWallet.setName(wallet.name);
}
realmWallet.setName(wallet.name);
realmWallet.setType(wallet.type);
realmWallet.setENSName(wallet.ENSname);
realmWallet.setBalance(wallet.balance);
realmWallet.setType(wallet.type);
realm.commitTransaction();
} catch (Exception e) {
Log.e(TAG, "storeWallet: " + e.getMessage(), e);
}
return updated;
return wallet;
});
}

@ -126,7 +126,7 @@ public class WalletRepository implements WalletRepositoryType
}
@Override
public Single<Integer> storeWallet(Wallet wallet)
public Single<Wallet> storeWallet(Wallet wallet)
{
return walletDataRealmSource.storeWallet(wallet);
}

@ -34,7 +34,7 @@ public interface WalletRepositoryType {
Single<Integer> storeWallets(Wallet[] wallets, boolean isMainNet);
Single<Integer> storeWallet(Wallet wallet);
Single<Wallet> storeWallet(Wallet wallet);
Single<String> getName(String address);
}

@ -2,6 +2,7 @@ package io.stormbird.wallet.repository.entity;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import io.stormbird.wallet.entity.Wallet;
/**
* Created by James on 8/11/2018.
@ -14,6 +15,7 @@ public class RealmWalletData extends RealmObject
private String ENSName;
private String balance;
private String name;
private int type;
public String getAddress()
{
@ -52,4 +54,7 @@ public class RealmWalletData extends RealmObject
public void setName(String name) {
this.name = name;
}
public Wallet.WalletType getType() { return Wallet.WalletType.values()[type]; }
public void setType(Wallet.WalletType type) { this.type = type.ordinal(); }
}

@ -0,0 +1,400 @@
package io.stormbird.wallet.service;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.security.keystore.*;
import android.util.Log;
import io.stormbird.token.tools.Numeric;
import io.stormbird.wallet.R;
import io.stormbird.wallet.entity.AuthenticationCallback;
import io.stormbird.wallet.entity.ServiceErrorException;
import io.stormbird.wallet.widget.SignTransactionDialog;
import org.web3j.crypto.Hash;
import wallet.core.jni.CoinType;
import wallet.core.jni.HDWallet;
import wallet.core.jni.PrivateKey;
import wallet.core.jni.PublicKey;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.ECGenParameterSpec;
import static io.stormbird.wallet.entity.ServiceErrorException.*;
@TargetApi(23)
public class HDKeyService implements AuthenticationCallback
{
private static final String TAG = "HDWallet";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String UNLOCK_KEY = "unlock_key";
private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
private static final int AUTHENTICATION_DURATION_SECONDS = 30;
private enum Operation
{
CREATE_HD_KEY, UNLOCK_HD_KEY
}
private static final int DEFAULT_KEY_STRENGTH = 128;
private final Activity context;
private HDWallet currentWallet;
private String currentKey;
private SignTransactionDialog signDialog;
public HDKeyService(Activity ctx)
{
System.loadLibrary("TrustWalletCore");
context = ctx;
}
//Create HDkey
public String createNewHDKey()
{
currentWallet = new HDWallet(DEFAULT_KEY_STRENGTH, "key1");
String mnemonic = currentWallet.mnemonic();
checkAuthentication(Operation.CREATE_HD_KEY);
PrivateKey pk = currentWallet.getKeyForCoin(CoinType.ETHEREUM);
return CoinType.ETHEREUM.deriveAddress(pk);
}
private void createHDKey2()
{
storeHDWallet(currentWallet);
}
public byte[] signData(String key, byte[] data)
{
//unlock
return null;
}
private synchronized byte[] signData(String data, String keyAddress) throws ServiceErrorException
{
KeyStore keyStore;
try
{
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
if (!keyStore.containsAlias(keyAddress)) return null;
String encryptedDataFilePath = getFilePath(context, keyAddress + "hd");
SecretKey secretKey = (SecretKey) keyStore.getKey(keyAddress, null);
if (secretKey == null)
{
/* no such key, the key is just simply not there */
boolean fileExists = new File(encryptedDataFilePath).exists();
if (!fileExists)
{
return null;/* file also not there, fine then */
}
throw new ServiceErrorException(
KEY_IS_GONE,
"file is present but the key is gone: " + keyAddress);
}
boolean ivExists = new File(getFilePath(context, currentKey + "iv")).exists();
byte[] iv = null;
if (ivExists) iv = readBytesFromFile(getFilePath(context, currentKey + "iv"));
if (iv == null || iv.length == 0)
{
throw new NullPointerException("iv is missing for " + currentKey + "iv");
}
Cipher outCipher = Cipher.getInstance(CIPHER_ALGORITHM);
outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(encryptedDataFilePath), outCipher);
byte[] mnemonicBytes = readBytesFromStream(cipherInputStream);
String mnemonic = new String(mnemonicBytes);
System.out.println(mnemonic);
return null;
}
catch (InvalidKeyException e)
{
if (e instanceof UserNotAuthenticatedException)
{
// showAuthenticationScreen(context, requestCode);
throw new ServiceErrorException(USER_NOT_AUTHENTICATED);
}
else
{
throw new ServiceErrorException(INVALID_KEY);
}
}
catch (IOException | CertificateException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e)
{
throw new ServiceErrorException(KEY_STORE_ERROR);
}
}
private synchronized boolean storeHDWallet(HDWallet wallet)
{
KeyStore keyStore;
try
{
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
PrivateKey pk = currentWallet.getKeyForCoin(CoinType.ETHEREUM);
currentKey = CoinType.ETHEREUM.deriveAddress(pk);
if (keyStore.containsAlias(currentKey)) { keyStore.deleteEntry(currentKey); } //TODO: Don't allow this
KeyGenerator keyGenerator = getMaxSecurityKeyGenerator();
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
final SecretKey secretKey = keyGenerator.generateKey();
final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
String path = getFilePath(context, currentKey + "hd");
Cipher inCipher = Cipher.getInstance(CIPHER_ALGORITHM);
inCipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = inCipher.getIV();
String ivPath = getFilePath(context, currentKey + "iv");
boolean success = writeBytesToFile(ivPath, iv);
if (!success) {
keyStore.deleteEntry(currentKey + "iv");
throw new ServiceErrorException(
ServiceErrorException.FAIL_TO_SAVE_IV_FILE,
"Failed to saveTokens the iv file for: " + currentKey + "iv");
}
try (CipherOutputStream cipherOutputStream = new CipherOutputStream(
new FileOutputStream(path),
inCipher))
{
cipherOutputStream.write(wallet.mnemonic().getBytes());
}
catch (Exception ex)
{
throw new ServiceErrorException(
ServiceErrorException.KEY_STORE_ERROR,
"Failed to saveTokens the file for: " + currentKey);
}
SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), ANDROID_KEY_STORE);
KeyInfo keyInfo = (KeyInfo)factory.getKeySpec(secretKey, KeyInfo.class);
if (keyInfo.isInsideSecureHardware())
{
System.out.println("Using hardware protected key");
}
else
{
System.out.println("Not using hardware protected key");
}
return true;
}
catch (UserNotAuthenticatedException e)
{
}
catch (Exception ex)
{
Log.d(TAG, "Key store error", ex);
//throw new ServiceErrorException(KEY_STORE_ERROR);
}
return false;
}
private KeyGenerator getMaxSecurityKeyGenerator()
{
KeyGenerator keyGenerator;
try
{
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
ANDROID_KEY_STORE);
}
catch (NoSuchAlgorithmException|NoSuchProviderException ex)
{
ex.printStackTrace();
return null;
}
try
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && tryInitStrongBoxKey(keyGenerator))
{
Log.d(TAG, "Using Strongbox");
}
else
{
//fallback to non Strongbox
tryInitUserAuthenticatedKey(keyGenerator);
}
}
catch (InvalidAlgorithmParameterException e)
{
e.printStackTrace();
keyGenerator = null;
}
catch (Exception e)
{
e.printStackTrace();
keyGenerator = null;
}
return keyGenerator;
}
private boolean tryInitStrongBoxKey(KeyGenerator keyGenerator) throws InvalidAlgorithmParameterException
{
try
{
keyGenerator.init(new KeyGenParameterSpec.Builder(
currentKey,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE)
.setKeySize(256)
.setUserAuthenticationRequired(true)
.setIsStrongBoxBacked(true)
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
.setRandomizedEncryptionRequired(true)
.setEncryptionPaddings(PADDING)
.build());
}
catch (StrongBoxUnavailableException e)
{
Log.d(TAG, "Android 9 device doesn't have StrongBox");
return false;
}
return true;
}
private void tryInitUserAuthenticatedKey(KeyGenerator keyGenerator) throws InvalidAlgorithmParameterException
{
keyGenerator.init(new KeyGenParameterSpec.Builder(
currentKey,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE)
.setKeySize(256)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
.setRandomizedEncryptionRequired(true)
.setEncryptionPaddings(PADDING)
.build());
}
private void checkAuthentication(Operation operation)
{
signDialog = new SignTransactionDialog(context, operation.ordinal());
signDialog.setBigText("Authenticate Credentials");
signDialog.setSecondaryButtonText(R.string.action_cancel);
signDialog.setPrimaryButtonText(R.string.dialog_title_sign_message);
signDialog.setCanceledOnTouchOutside(true);
signDialog.setOnCancelListener(v -> { authenticateFail("Cancelled"); });
signDialog.show();
signDialog.getFingerprintAuthorisation(this);
}
private synchronized static String getFilePath(Context context, String fileName) {
return new File(context.getFilesDir(), fileName).getAbsolutePath();
}
private static boolean writeBytesToFile(String path, byte[] data) {
FileOutputStream fos = null;
try {
File file = new File(path);
fos = new FileOutputStream(file);
// Writes bytes from the specified byte array to this file output stream
fos.write(data);
return true;
} catch (FileNotFoundException e) {
System.out.println("File not found" + e);
} catch (IOException ioe) {
System.out.println("Exception while writing file " + ioe);
} finally {
// close the streams using close method
try {
if (fos != null) {
fos.close();
}
} catch (IOException ioe) {
System.out.println("Error while closing stream: " + ioe);
}
}
return false;
}
private static byte[] readBytesFromFile(String path) {
byte[] bytes = null;
FileInputStream fin;
try {
File file = new File(path);
fin = new FileInputStream(file);
bytes = readBytesFromStream(fin);
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
private static byte[] readBytesFromStream(InputStream in) {
// this dynamically extends to take the bytes you read
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
// this is storage overwritten on each iteration with bytes
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
// we need to know how may bytes were read to write them to the byteBuffer
int len;
try {
while ((len = in.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
byteBuffer.close();
} catch (IOException e) {
e.printStackTrace();
}
if (in != null) try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// and then we can return your byte array.
return byteBuffer.toByteArray();
}
@Override
public void authenticatePass(int callbackId)
{
signDialog.dismiss();
//resume key operation
Operation operation = Operation.values()[callbackId];
switch (operation)
{
case CREATE_HD_KEY:
createHDKey2();
break;
case UNLOCK_HD_KEY:
break;
default:
break;
}
}
@Override
public void authenticateFail(String fail)
{
signDialog.dismiss();
//TODO: display fail dialog
}
}

@ -0,0 +1,21 @@
package io.stormbird.wallet.ui;
import android.os.Bundle;
import android.support.annotation.Nullable;
import io.stormbird.wallet.R;
public class BackupSeedPhrase extends BaseActivity
{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_backup_seed);
toolbar();
setTitle(R.string.backup_seed_phrase);
}
}

@ -4,14 +4,17 @@ package io.stormbird.wallet.ui;
import android.Manifest;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -32,7 +35,9 @@ import io.stormbird.wallet.viewmodel.NewSettingsViewModelFactory;
import io.stormbird.wallet.widget.AWalletConfirmationDialog;
import io.stormbird.wallet.widget.SelectLocaleDialog;
import static io.stormbird.wallet.C.CHANGED_LOCALE;
import java.util.Set;
import static io.stormbird.wallet.C.*;
import static io.stormbird.wallet.ui.HomeActivity.RC_ASSET_EXTERNAL_WRITE_PERM;
public class NewSettingsFragment extends Fragment {
@ -40,7 +45,6 @@ public class NewSettingsFragment extends Fragment {
NewSettingsViewModelFactory newSettingsViewModelFactory;
private NewSettingsViewModel viewModel;
private Wallet wallet;
private TextView walletsSubtext;
private TextView localeSubtext;
@ -144,6 +148,21 @@ public class NewSettingsFragment extends Fragment {
updateNotificationState();
});
final LinearLayout layoutBackup = view.findViewById(R.id.layout_backup_text);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
Set<String> notBackedUp = pref.getStringSet ("notbackedup", new ArraySet<>());
if (notBackedUp.size() > 0)
{
layoutBackup.setVisibility(View.VISIBLE);
layoutBackup.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), BackupSeedPhrase.class);
String addr = notBackedUp.iterator().next();
intent.putExtra("ADDRESS", addr);
intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
getActivity().startActivity(intent);
});
}
LinearLayout layoutFacebook = view.findViewById(R.id.layout_facebook);
layoutFacebook.setOnClickListener(v -> {
Intent intent;
@ -184,7 +203,6 @@ public class NewSettingsFragment extends Fragment {
}
private void onDefaultWallet(Wallet wallet) {
this.wallet = wallet;
walletsSubtext.setText(wallet.address);
}

@ -130,7 +130,7 @@ public class SplashActivity extends BaseActivity {
}
else
{
splashViewModel.createNewWallet();
splashViewModel.createNewWallet(this);
}
}
else

@ -28,6 +28,7 @@ import io.stormbird.wallet.R;
import io.stormbird.wallet.entity.ErrorEnvelope;
import io.stormbird.wallet.entity.NetworkInfo;
import io.stormbird.wallet.entity.Wallet;
import io.stormbird.wallet.service.HDKeyService;
import io.stormbird.wallet.ui.widget.adapter.WalletsAdapter;
import io.stormbird.wallet.viewmodel.WalletsViewModel;
import io.stormbird.wallet.viewmodel.WalletsViewModelFactory;
@ -201,7 +202,7 @@ public class WalletsActivity extends BaseActivity implements
@Override
public void onNewWallet(View view) {
hideDialog();
viewModel.newWallet();
viewModel.newWallet(this);
}
@Override

@ -1,48 +0,0 @@
package io.stormbird.wallet.util;
import android.content.Context;
import android.os.Build;
import io.stormbird.wallet.entity.ServiceErrorException;
import com.wallet.pwd.trustapp.PasswordManager;
import java.security.SecureRandom;
public class PasswordStoreFactory {
public static void put(Context context, String address, String password) throws ServiceErrorException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KS.put(context, address, password);
} else {
try {
PasswordManager.setPassword(address, password, context);
} catch (Exception e) {
throw new ServiceErrorException(ServiceErrorException.KEY_STORE_ERROR);
}
}
}
public static byte[] get(Context context, String address) throws ServiceErrorException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return KS.get(context, address);
} else {
try {
return PasswordManager.getPassword(address, context).getBytes();
} catch (Exception e) {
throw new ServiceErrorException(ServiceErrorException.KEY_STORE_ERROR);
}
}
}
public static void showAuthenticationScreen(Context context, int unlockScreenRequest) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KS.showAuthenticationScreen(context, unlockScreenRequest);
}
}
public static String generatePassword() {
byte bytes[] = new byte[256];
SecureRandom random = new SecureRandom();
random.nextBytes(bytes);
return String.valueOf(bytes);
}
}

@ -1,5 +1,6 @@
package io.stormbird.wallet.viewmodel;
import android.app.Activity;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
@ -69,11 +70,11 @@ public class SplashViewModel extends ViewModel {
return createWallet;
}
public void createNewWallet()
public void createNewWallet(Activity ctx)
{
//create a new wallet for the user
createWalletInteract
.create()
.create(ctx)
.subscribe(account -> {
fetchWallets();
createWallet.postValue(account);

@ -112,10 +112,10 @@ public class WalletActionsViewModel extends BaseViewModel {
.subscribe(this::onStored, this::onError);
}
private void onStored(Integer count) {
private void onStored(Wallet wallet) {
isTaskRunning.postValue(false);
Log.d(TAG, "Stored " + count + " Wallets");
saved.postValue(count);
Log.d(TAG, "Stored " + wallet.address);
saved.postValue(1);
}
@Override

@ -188,15 +188,16 @@ public class WalletsViewModel extends BaseViewModel
findNetwork();
}
public void newWallet()
public void newWallet(Activity ctx)
{
progress.setValue(true);
createWalletInteract
.create()
.create(ctx)
.flatMap(fetchWalletsInteract::storeWallet)
.subscribe(account -> {
fetchWallets();
createdWallet.postValue(account);
}, this::onCreateWalletError);
}, this::onCreateWalletError).isDisposed();
}
/**
@ -243,11 +244,14 @@ public class WalletsViewModel extends BaseViewModel
//update names for wallets
//got names?
//preserve order
for (Wallet w : wallets.getValue())
if (wallets.getValue() != null)
{
if (update.wallets.containsKey(w.address))
for (Wallet w : wallets.getValue())
{
w.ENSname = update.wallets.get(w.address).ENSname;
if (update.wallets.containsKey(w.address))
{
w.ENSname = update.wallets.get(w.address).ENSname;
}
}
}

@ -23,9 +23,9 @@ public class AWalletConfirmationDialog extends Dialog {
private TextView mediumText;
private TextView bigText;
private TextView extraText;
private Button btnPrimary;
private Button btnSecondary;
private Context context;
protected Button btnPrimary;
protected Button btnSecondary;
protected Context context;
public AWalletConfirmationDialog(@NonNull Activity activity) {
super(activity);

@ -0,0 +1,120 @@
package io.stormbird.wallet.widget;
import android.Manifest;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import io.stormbird.wallet.R;
import io.stormbird.wallet.entity.AuthenticationCallback;
/**
* Created by James on 7/06/2019.
* Stormbird in Sydney
*/
public class SignTransactionDialog extends AWalletConfirmationDialog
{
private int callBackId;
public SignTransactionDialog(@NonNull Activity activity, int callBackId)
{
super(activity);
//display fingerprint
ImageView fingerPrint = findViewById(R.id.image_fingerprint);
fingerPrint.setVisibility(View.VISIBLE);
findViewById(R.id.dialog_button1_container).setVisibility(View.GONE);
this.callBackId = callBackId;
}
//TODO: Needs to be 'use PIN'
@Override
public void setPrimaryButtonText(int resId)
{
btnPrimary.setText(context.getResources().getString(resId));
}
//get fingerprint or PIN
public void getFingerprintAuthorisation(AuthenticationCallback authCallback) {
// Create the Confirm Credentials screen. You can customize the title and description. Or
// we will provide a generic one for you if you leave it null
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
FingerprintManager fpManager = fingerprintUnlockSupported(context);
if (fpManager != null)
{
authenticate(fpManager, context, authCallback);
}
else
{
//remove fingerprint
ImageView fingerPrint = findViewById(R.id.image_fingerprint);
fingerPrint.setVisibility(View.GONE);
Button pin = findViewById(R.id.dialog_button1_container);
pin.setVisibility(View.VISIBLE);
}
// Intent intent = km.createConfirmDeviceCredentialIntent(null, null);
// if (intent != null) {
// Activity app = (Activity) context;
// app.startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
// }
}
private void authenticate(FingerprintManager fpManager, Context context, AuthenticationCallback authCallback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return;
CancellationSignal cancellationSignal;
cancellationSignal = new CancellationSignal();
fpManager.authenticate(null, cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
Toast.makeText(context, errString, Toast.LENGTH_SHORT).show();
authCallback.authenticateFail(errString.toString());
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
super.onAuthenticationHelp(helpCode, helpString);
//Toast.makeText(context, helpString.toString(), Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
authCallback.authenticatePass(callBackId);
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
authCallback.authenticateFail("Authentication Failure");
}
}, null);
}
private static FingerprintManager fingerprintUnlockSupported(Context ctx)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return null;
}
if (ctx.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return null;
}
FingerprintManager fpManager = (FingerprintManager) ctx.getSystemService(Context.FINGERPRINT_SERVICE);
if (fpManager == null || !fpManager.isHardwareDetected() || !fpManager.hasEnrolledFingerprints()) {
return null;
}
return fpManager;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="20dp" />
<corners android:radius="2dp" />
</shape>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/warning_red" />
<corners android:radius="2dp" />
</shape>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/background_warning_event" android:state_pressed="true"/>
<item android:drawable="@drawable/background_warning_event"/>
</selector>

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
android:orientation="vertical">
<include layout="@layout/layout_simple_toolbar" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_card">
<ImageView
android:id="@+id/seed_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:background="@drawable/seed_graphic" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:fontFamily="@font/font_light"
android:layout_centerHorizontal="true"
android:layout_above="@id/seed_image"
android:gravity="center_horizontal"
android:layout_marginBottom="50dp"
android:text="Back up your wallet\nwith seed phrase"
android:textColor="@color/black"
android:textSize="28sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/seed_image"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:gravity="center_horizontal"
android:text="Making backup is very simple and safe:\njust write down 12 words and\nkeep them in a safe place, offline." />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/nasty_green"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="25dp"
android:text="back up my wallet"
android:textStyle="normal"
android:textColor="@color/white"
android:letterSpacing="0.04"
android:layout_alignParentBottom="true"
android:textSize="16sp"
android:textAllCaps="true" />
<io.stormbird.wallet.widget.ProgressView
android:id="@+id/progress_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
tools:visibility="gone" />
</RelativeLayout>
<io.stormbird.wallet.widget.SystemView
android:id="@+id/system_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?actionBarSize"
android:background="@color/white"
tools:visibility="gone" />
</LinearLayout>

@ -82,12 +82,23 @@
android:textColor="@color/text_black"
android:textSize="25sp" />
<ImageView
android:id="@+id/image_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:visibility="visible"
android:layout_gravity="center"
android:contentDescription="@string/action_share"
android:background="@drawable/ic_shortcut_fingerprint" />
<LinearLayout
android:id="@+id/dialog_button1_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center"
android:visibility="gone"
android:background="@drawable/background_round_primary">
<Button
@ -100,6 +111,7 @@
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:textAllCaps="false"
android:visibility="visible"
android:textColor="@color/white"
android:textSize="20sp" />

@ -10,6 +10,50 @@
android:orientation="vertical"
android:paddingBottom="15dp">
<LinearLayout
android:id="@+id/layout_backup_text"
android:layout_width="match_parent"
android:layout_height="167dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"
android:paddingStart="21dp"
android:paddingTop="20dp"
android:background="@drawable/backup_warning_red"
android:clickable="true"
android:orientation="vertical"
android:visibility="gone"
android:focusable="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/font_regular"
android:text="@string/wallet_not_backed_up"
android:textColor="@color/white"
android:textSize="22sp" />
<TextView
android:layout_marginTop="9dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/font_regular"
android:text="@string/not_backed_up_detail"
android:textColor="@color/white"
android:textSize="12sp" />
<Button
android:layout_marginTop="19dp"
android:layout_width="185dp"
android:layout_height="37dp"
android:fontFamily="@font/font_regular"
android:background="@color/warning_dark_red"
android:text="@string/back_up_wallet_action"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_wallet_address"
android:layout_width="match_parent"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -89,4 +89,7 @@
<string name="token_requirement">This function requires %1$s tokens</string>
<string name="contains_no_recipient">Transaction has no recipient</string>
<string name="contains_no_value">DApp transaction contains no value.</string>
<string name="wallet_not_backed_up">Your Wallet is not backed up!</string>
<string name="not_backed_up_detail">You have not backed up your wallet yet. You have $1,000 USD net. Act now.</string>
<string name="backup_seed_phrase">Backup Seed Phrase</string>
</resources>

@ -351,4 +351,7 @@
<string name="token_requirement">This function requires %1$s tokens</string>
<string name="contains_no_recipient">Transaction has no recipient</string>
<string name="contains_no_value">DApp transaction contains no value.</string>
<string name="wallet_not_backed_up">Your Wallet is not backed up!</string>
<string name="not_backed_up_detail">You have not backed up your wallet yet. You have $1,000 USD net. Act now.</string>
<string name="backup_seed_phrase">Backup Seed Phrase</string>
</resources>

@ -42,6 +42,9 @@
<color name="disabled_color">#888888</color>
<color name="cancel_red">#db393a</color>
<color name="dark_yellow">#e1b706</color>
<color name="warning_red">#c34840</color>
<color name="warning_dark_red">#7f342f</color>
<color name="nasty_green">#75b943</color>
<color name="viewfinder_mask">#60000000</color>
<color name="viewfinder_laser">#ffcc0000</color>

@ -492,4 +492,8 @@
<string name="token_requirement">This function requires %1$s tokens</string>
<string name="contains_no_recipient">Transaction has no recipient</string>
<string name="contains_no_value">DApp transaction contains no value.</string>
<string name="wallet_not_backed_up">Your Wallet is not backed up!</string>
<string name="not_backed_up_detail">You have not backed up your wallet yet. You have $1,000 USD net. Act now.</string>
<string name="back_up_wallet_action">BACK UP WALLET →</string>
<string name="backup_seed_phrase">Backup Seed Phrase</string>
</resources>

@ -13,12 +13,12 @@ buildscript {
// don't do that. add that repository to app/build.gradle
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath "io.realm:realm-gradle-plugin:5.10.0"
classpath 'com.android.tools.build:gradle:3.4.2'
classpath "io.realm:realm-gradle-plugin:5.12.0"
// WARNING WARNING WARNING
// you are about to add here a dependency to be used in the Android app
// don't do that. add that dependency to app/build.gradle
classpath 'com.google.gms:google-services:4.2.0'
classpath 'com.google.gms:google-services:4.3.0'
}
}

@ -1,6 +1,6 @@
#Sun Apr 14 18:17:45 AEST 2019
#Wed Jul 10 11:34:33 AEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

Loading…
Cancel
Save