diff --git a/wallet/res/menu/wallet_safety_options.xml b/wallet/res/menu/wallet_safety_options.xml
index 2d5ab8e826..672274944b 100644
--- a/wallet/res/menu/wallet_safety_options.xml
+++ b/wallet/res/menu/wallet_safety_options.xml
@@ -13,6 +13,14 @@
android:id="@+id/wallet_options_restore_wallet"
android:title="@string/import_keys_dialog_title"
app:showAsAction="never" />
+
+
- Disconnect
Masternodes
+ View Wallet Seed
+ Restore Wallet from Seed
+ This is your recovery phrase. Please write it down.
+ DO NOT let anyone see your recovery phrase or they can spend your Dash. This phrase is not compatible with other Dash Software.
+ Enter your recovery phrase here.
+ Recovery phrase goes here.
+
+
diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java
index ccd8b7a69a..d7a3e1e34c 100644
--- a/wallet/src/de/schildbach/wallet/WalletApplication.java
+++ b/wallet/src/de/schildbach/wallet/WalletApplication.java
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import org.bitcoinj.core.CoinDefinition;
@@ -31,6 +32,7 @@
import org.bitcoinj.core.VersionMessage;
import org.bitcoinj.crypto.LinuxSecureRandom;
import org.bitcoinj.crypto.MnemonicCode;
+import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.store.FlatDB;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.Protos;
@@ -69,6 +71,8 @@
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import static de.schildbach.wallet.Constants.HEX;
+
/**
* @author Andreas Schildbach
*/
diff --git a/wallet/src/de/schildbach/wallet/ui/WalletActivity.java b/wallet/src/de/schildbach/wallet/ui/WalletActivity.java
index 6f065ef015..5110fcbbd2 100644
--- a/wallet/src/de/schildbach/wallet/ui/WalletActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/WalletActivity.java
@@ -24,6 +24,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
@@ -32,6 +34,8 @@
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.VersionedChecksummedBytes;
+import org.bitcoinj.crypto.MnemonicCode;
+import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.Wallet.BalanceType;
@@ -104,6 +108,7 @@ public final class WalletActivity extends AbstractWalletActivity implements Acti
private static final int DIALOG_TIMESKEW_ALERT = 3;
private static final int DIALOG_VERSION_ALERT = 4;
private static final int DIALOG_LOW_STORAGE_ALERT = 5;
+ private static final int DIALOG_RESTORE_WALLET_FROM_SEED = 12;
private WalletApplication application;
private Configuration config;
@@ -400,6 +405,18 @@ public void handleRestoreWallet() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_RESTORE_WALLET);
}
+ public void handleBackupWalletToSeed() {
+ //if (ContextCompat.checkSelfPermission(this,
+ // Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
+ BackupWalletToSeedDialogFragment.show(getFragmentManager());
+ //else
+ // ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ // REQUEST_CODE_BACKUP_WALLET);
+ }
+
+ public void handleRestoreWalletFromSeed() {
+ showDialog(DIALOG_RESTORE_WALLET_FROM_SEED);
+ }
public void handleEncryptKeys() {
EncryptKeysDialogFragment.show(getFragmentManager());
@@ -449,6 +466,8 @@ else if (id == DIALOG_VERSION_ALERT)
return createVersionAlertDialog();
else if (id == DIALOG_LOW_STORAGE_ALERT)
return createLowStorageAlertDialog();
+ else if (id == DIALOG_RESTORE_WALLET_FROM_SEED)
+ return createRestoreWalletFromSeedDialog();
else
throw new IllegalArgumentException();
}
@@ -457,6 +476,8 @@ else if (id == DIALOG_LOW_STORAGE_ALERT)
protected void onPrepareDialog(final int id, final Dialog dialog) {
if (id == DIALOG_RESTORE_WALLET)
prepareRestoreWalletDialog(dialog);
+ else if(id == DIALOG_RESTORE_WALLET_FROM_SEED)
+ prepareRestoreWalletFromSeedDialog(dialog);
}
private Dialog createBackupWalletPermissionDialog() {
@@ -615,7 +636,114 @@ protected boolean needsPassword() {
final CheckBox showView = (CheckBox) alertDialog.findViewById(R.id.import_keys_from_storage_show);
showView.setOnCheckedChangeListener(new ShowPasswordCheckListener(passwordView));
}
+ private Dialog createRestoreWalletFromSeedDialog() {
+ final View view = getLayoutInflater().inflate(R.layout.restore_wallet_from_seed_dialog, null);
+ final TextView messageView = (TextView) view.findViewById(R.id.restore_wallet_dialog_message);
+ //final Spinner fileView = (Spinner) view.findViewById(R.id.import_keys_from_storage_file);
+ final EditText passwordView = (EditText) view.findViewById(R.id.import_seed_recovery_phrase);
+
+ final DialogBuilder dialog = new DialogBuilder(this);
+ dialog.setTitle(R.string.import_keys_dialog_title_from_seed);
+ dialog.setView(view);
+ dialog.setPositiveButton(R.string.import_keys_dialog_button_import, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ //final File file = (File) fileView.getSelectedItem();
+ final String password = passwordView.getText().toString().trim();
+ List words = new ArrayList(Arrays.asList(password.split(" ")));
+
+ passwordView.setText(null); // get rid of it asap
+
+ //if (WalletUtils.BACKUP_FILE_FILTER.accept(file))
+ // restoreWalletFromProtobuf(file);
+ //else if (WalletUtils.KEYS_FILE_FILTER.accept(file))
+ // restorePrivateKeysFromBase58(file);
+ //else if (Crypto.OPENSSL_FILE_FILTER.accept(file))
+ // restoreWalletFromEncrypted(file, password);
+ restoreWalletFromSeed(words);
+
+ }
+ });
+ dialog.setNegativeButton(R.string.button_cancel, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ passwordView.setText(null); // get rid of it asap
+ }
+ });
+ dialog.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(final DialogInterface dialog) {
+ passwordView.setText(null); // get rid of it asap
+ }
+ });
+ /*final String path;
+ final String backupPath = Constants.Files.EXTERNAL_WALLET_BACKUP_DIR.getAbsolutePath();
+ final String storagePath = Constants.Files.EXTERNAL_STORAGE_DIR.getAbsolutePath();
+ if (backupPath.startsWith(storagePath))
+ path = backupPath.substring(storagePath.length());
+ else
+ path = backupPath;
+ */
+ messageView.setText(getString(R.string.import_keys_from_seed_dialog_message));
+
+ //fileView.setAdapter(adapter);
+
+ return dialog.create();
+ }
+
+ private void prepareRestoreWalletFromSeedDialog(final Dialog dialog) {
+ final AlertDialog alertDialog = (AlertDialog) dialog;
+
+ final List files = new LinkedList();
+
+ // external storage
+ final File[] externalFiles = Constants.Files.EXTERNAL_WALLET_BACKUP_DIR.listFiles();
+ if (externalFiles != null)
+ for (final File file : externalFiles)
+ if (Crypto.OPENSSL_FILE_FILTER.accept(file))
+ files.add(file);
+
+ // internal storage
+ for (final String filename : fileList())
+ if (filename.startsWith(Constants.Files.WALLET_KEY_BACKUP_PROTOBUF + '.'))
+ files.add(new File(getFilesDir(), filename));
+
+ // sort
+ Collections.sort(files, new Comparator() {
+ @Override
+ public int compare(final File lhs, final File rhs) {
+ return lhs.getName().compareToIgnoreCase(rhs.getName());
+ }
+ });
+
+ final View replaceWarningView = alertDialog
+ .findViewById(R.id.restore_wallet_from_storage_dialog_replace_warning);
+ final boolean hasCoins = wallet.getBalance(BalanceType.ESTIMATED).signum() > 0;
+ replaceWarningView.setVisibility(hasCoins ? View.VISIBLE : View.GONE);
+
+ final EditText passwordView = (EditText) alertDialog.findViewById(R.id.import_seed_recovery_phrase);
+ passwordView.setText(null);
+
+ final ImportDialogButtonEnablerListener dialogButtonEnabler = new ImportDialogButtonEnablerListener(
+ passwordView, alertDialog) {
+
+ @Override
+ protected boolean hasFile()
+ {
+ return true;
+ }
+ @Override
+ protected boolean needsPassword() {
+ return true;
+ }
+ };
+ passwordView.addTextChangedListener(dialogButtonEnabler);
+ //fileView.setOnItemSelectedListener(dialogButtonEnabler);
+
+ //final CheckBox showView = (CheckBox) alertDialog.findViewById(R.id.import_keys_from_storage_show);
+ //showView.setOnCheckedChangeListener(new ShowPasswordCheckListener(passwordView));
+ }
private void checkLowStorageAlert() {
final Intent stickyIntent = registerReceiver(null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
if (stickyIntent != null)
@@ -870,6 +998,51 @@ public void onClick(final DialogInterface dialog, final int id) {
}
}
+ private void restoreWalletFromSeed(final List words) {
+ FileInputStream is = null;
+ try {
+ //is = new FileInputStream(file);
+ MnemonicCode.INSTANCE.check(words);
+ restoreWallet(WalletUtils.restoreWalletFromSeed(words, Constants.NETWORK_PARAMETERS));
+
+ log.info("successfully restored wallet from seed: {}", words.size());
+ } catch (final IOException x) {
+ final DialogBuilder dialog = DialogBuilder.warn(this, R.string.import_export_keys_dialog_failure_title);
+ dialog.setMessage(getString(R.string.import_keys_dialog_failure, x.getMessage()));
+ dialog.setPositiveButton(R.string.button_dismiss, null);
+ dialog.setNegativeButton(R.string.button_retry, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int id) {
+ showDialog(DIALOG_RESTORE_WALLET);
+ }
+ });
+ dialog.show();
+
+ log.info("problem restoring wallet from seed: ", x);
+ } catch (MnemonicException x) {
+ final DialogBuilder dialog = DialogBuilder.warn(this, R.string.import_export_keys_dialog_failure_title);
+ dialog.setMessage(getString(R.string.import_keys_dialog_failure, x.getMessage()));
+ dialog.setPositiveButton(R.string.button_dismiss, null);
+ dialog.setNegativeButton(R.string.button_retry, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int id) {
+ showDialog(DIALOG_RESTORE_WALLET);
+ }
+ });
+ dialog.show();
+
+ log.info("problem restoring wallet from seed: ", x);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException x2) {
+ // swallow
+ }
+ }
+ }
+ }
+
private void restorePrivateKeysFromBase58(final File file) {
FileInputStream is = null;
try {
@@ -957,6 +1130,13 @@ public boolean onContextItemSelected(MenuItem item) {
case R.id.wallet_options_encrypt_keys:
handleEncryptKeys();
return true;
+ case R.id.wallet_options_backup_wallet_to_seed:
+ handleBackupWalletToSeed();
+ return true;
+
+ case R.id.wallet_options_restore_wallet_from_seed:
+ handleRestoreWalletFromSeed();
+ return true;
}
return super.onContextItemSelected(item);
diff --git a/wallet/src/de/schildbach/wallet/util/WalletUtils.java b/wallet/src/de/schildbach/wallet/util/WalletUtils.java
index c4a0e5ad1e..fce693c385 100644
--- a/wallet/src/de/schildbach/wallet/util/WalletUtils.java
+++ b/wallet/src/de/schildbach/wallet/util/WalletUtils.java
@@ -46,7 +46,10 @@
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
+import org.bitcoinj.crypto.MnemonicCode;
+import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.script.Script;
+import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.KeyChainGroup;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.bitcoinj.wallet.Wallet;
@@ -189,6 +192,30 @@ public static Wallet restoreWalletFromProtobuf(final InputStream is,
}
}
+ public static Wallet restoreWalletFromSeed(final List words,
+ final NetworkParameters expectedNetworkParameters) throws IOException {
+ try {
+ final Wallet wallet = new Wallet(Constants.NETWORK_PARAMETERS, new KeyChainGroup(Constants.NETWORK_PARAMETERS, new DeterministicSeed(words, null,"", 1474771804)));//new WalletProtobufSerializer().readWallet(is, true, null);
+
+ if (!wallet.getParams().equals(expectedNetworkParameters))
+ throw new IOException("bad wallet backup network parameters: " + wallet.getParams().getId());
+ if (!wallet.isConsistent())
+ throw new IOException("inconsistent wallet backup");
+
+ return wallet;
+ } /*catch (final MnemonicException.MnemonicLengthException x) {
+ throw new IOException("Mnemonic Seed has the incorrect number of words", x);
+ } catch (final MnemonicException.MnemonicChecksumException x) {
+ throw new IOException("Mnemonic Seed has the incorrect checksum", x);
+ } catch (final MnemonicException.MnemonicWordException x) {
+ throw new IOException("Mnemonic Seed has an invalid word", x);
+ }*/
+ finally {
+
+ }
+
+ }
+
public static Wallet restorePrivateKeysFromBase58(final InputStream is,
final NetworkParameters expectedNetworkParameters) throws IOException {
final BufferedReader keyReader = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8));