terminal - create bip39 wallet

This commit is contained in:
Craig Raw 2022-10-19 09:44:44 +02:00
parent 8dd1850905
commit 8eb092a8d6
6 changed files with 540 additions and 72 deletions

View file

@ -4,6 +4,7 @@ import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.ActionListBox;
import com.googlecode.lanterna.gui2.dialogs.ActionListDialogBuilder;
import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder;
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.io.Config;
@ -11,8 +12,8 @@ import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.terminal.preferences.GeneralDialog;
import com.sparrowwallet.sparrow.terminal.preferences.ServerStatusDialog;
import com.sparrowwallet.sparrow.terminal.preferences.ServerTypeDialog;
import com.sparrowwallet.sparrow.terminal.wallet.Bip39Dialog;
import com.sparrowwallet.sparrow.terminal.wallet.LoadWallet;
import javafx.application.Platform;
import java.io.File;
import java.util.Map;
@ -25,34 +26,32 @@ public class MasterActionListBox extends ActionListBox {
super(new TerminalSize(14, 3));
addItem("Wallets", () -> {
ActionListDialogBuilder builder = new ActionListDialogBuilder();
builder.setTitle("Wallets");
if(Config.get().getRecentWalletFiles() != null) {
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) {
File recentWalletFile = Config.get().getRecentWalletFiles().get(i);
Storage storage = new Storage(recentWalletFile);
ActionListDialogBuilder builder = new ActionListDialogBuilder();
builder.setTitle("Wallets");
if(Config.get().getRecentWalletFiles() != null) {
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) {
File recentWalletFile = Config.get().getRecentWalletFiles().get(i);
Storage storage = new Storage(recentWalletFile);
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream()
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey)
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst();
if(optWallet.isPresent()) {
builder.addAction(storage.getWalletName(null) + "*", () -> LoadWallet.getOpeningDialog(storage, optWallet.get()).showDialog(SparrowTerminal.get().getGui()));
} else {
builder.addAction(storage.getWalletName(null), new LoadWallet(storage));
}
}
}
builder.addAction("Open Wallet...", () -> {
FileDialogBuilder openBuilder = new FileDialogBuilder().setTitle("Open Wallet");
openBuilder.setShowHiddenDirectories(true);
openBuilder.setSelectedFile(Storage.getWalletsDir());
File file = openBuilder.build().showDialog(SparrowTerminal.get().getGui());
if(file != null) {
LoadWallet loadWallet = new LoadWallet(new Storage(file));
SparrowTerminal.get().getGuiThread().invokeLater(loadWallet);
}
});
builder.build().showDialog(SparrowTerminal.get().getGui());
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream()
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey)
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst();
if(optWallet.isPresent()) {
builder.addAction(storage.getWalletName(null) + "*", () -> {
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, optWallet.get()).showDialog(SparrowTerminal.get().getGui()));
});
} else {
builder.addAction(storage.getWalletName(null), new LoadWallet(storage));
}
}
}
builder.addAction("Open Wallet...", () -> {
SparrowTerminal.get().getGuiThread().invokeLater(MasterActionListBox::openWallet);
});
builder.addAction("Create Wallet...", () -> {
SparrowTerminal.get().getGuiThread().invokeLater(MasterActionListBox::createWallet);
});
builder.build().showDialog(SparrowTerminal.get().getGui());
});
addItem("Preferences", () -> {
@ -77,4 +76,36 @@ public class MasterActionListBox extends ActionListBox {
addItem("Quit", () -> sparrowTerminal.getGui().getMainWindow().close());
}
private static void openWallet() {
FileDialogBuilder openBuilder = new FileDialogBuilder().setTitle("Open Wallet");
openBuilder.setShowHiddenDirectories(true);
openBuilder.setSelectedFile(Storage.getWalletsDir());
File file = openBuilder.build().showDialog(SparrowTerminal.get().getGui());
if(file != null) {
LoadWallet loadWallet = new LoadWallet(new Storage(file));
SparrowTerminal.get().getGuiThread().invokeLater(loadWallet);
}
}
private static void createWallet() {
TextInputDialogBuilder newWalletNameBuilder = new TextInputDialogBuilder();
newWalletNameBuilder.setTitle("Create Wallet");
newWalletNameBuilder.setDescription("Enter a name for the wallet");
newWalletNameBuilder.setValidator(content -> Storage.walletExists(content) ? "Wallet already exists" : null);
String walletName = newWalletNameBuilder.build().showDialog(SparrowTerminal.get().getGui());
ActionListDialogBuilder newBuilder = new ActionListDialogBuilder();
newBuilder.setTitle("Create Wallet");
newBuilder.setDescription("Choose the type of wallet");
newBuilder.addAction("Software (BIP39)", () -> {
Bip39Dialog bip39Dialog = new Bip39Dialog(walletName);
bip39Dialog.showDialog(SparrowTerminal.get().getGui());
});
newBuilder.addAction("Watch Only", () -> {
//OutputDescriptorDialog outputDescriptorDialog = new OutputDescriptorDialog(walletName);
});
newBuilder.build().showDialog(SparrowTerminal.get().getGui());
}
}

View file

@ -9,18 +9,26 @@ import com.googlecode.lanterna.screen.TerminalScreen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.Terminal;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.SparrowWallet;
import com.sparrowwallet.sparrow.*;
import com.sparrowwallet.sparrow.event.OpenWalletsEvent;
import com.sparrowwallet.sparrow.event.WalletOpenedEvent;
import com.sparrowwallet.sparrow.event.WalletOpeningEvent;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.terminal.wallet.WalletData;
import com.sparrowwallet.sparrow.wallet.WalletForm;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS;
public class SparrowTerminal extends Application {
private static final Logger log = LoggerFactory.getLogger(SparrowTerminal.class);
@ -33,6 +41,8 @@ public class SparrowTerminal extends Application {
private final Map<String, WalletData> walletData = new HashMap<>();
private static final javafx.stage.Window DEFAULT_WINDOW = new Window() { };
@Override
public void init() throws Exception {
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
@ -101,4 +111,32 @@ public class SparrowTerminal extends Application {
public static SparrowTerminal get() {
return sparrowTerminal;
}
public static void addWallet(Storage storage, Wallet wallet) {
if(wallet.isNested()) {
WalletData walletData = SparrowTerminal.get().getWalletData().get(storage.getWalletId(wallet.getMasterWallet()));
WalletForm walletForm = new WalletForm(storage, wallet);
EventManager.get().register(walletForm);
walletData.getWalletForm().getNestedWalletForms().add(walletForm);
} else {
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
WalletForm walletForm = new WalletForm(storage, wallet);
EventManager.get().register(walletForm);
SparrowTerminal.get().getWalletData().put(walletForm.getWalletId(), new WalletData(walletForm));
List<WalletTabData> walletTabDataList = SparrowTerminal.get().getWalletData().values().stream()
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList());
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList));
Set<File> walletFiles = new LinkedHashSet<>();
walletFiles.add(storage.getWalletFile());
if(Config.get().getRecentWalletFiles() != null) {
walletFiles.addAll(Config.get().getRecentWalletFiles().stream().limit(MAX_RECENT_WALLETS - 1).collect(Collectors.toList()));
}
Config.get().setRecentWalletFiles(Config.get().isLoadRecentWallets() ? new ArrayList<>(walletFiles) : Collections.emptyList());
}
EventManager.get().post(new WalletOpenedEvent(storage, wallet));
}
}

View file

@ -0,0 +1,270 @@
package com.sparrowwallet.sparrow.terminal.wallet;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.io.Bip39;
import com.sparrowwallet.sparrow.io.ImportException;
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Bip39Dialog extends NewWalletDialog {
private static final Logger log = LoggerFactory.getLogger(Bip39Dialog.class);
public static final int MAX_COLUMNS = 40;
private final Bip39 importer = new Bip39();
private Wallet wallet;
private final String walletName;
private final ComboBox<DisplayScriptType> scriptType;
private final TextBox seedWords;
private final TextBox passphrase;
private final Button createWallet;
public Bip39Dialog(String walletName) {
super("Create BIP39 Wallet - " + walletName);
setHints(List.of(Hint.CENTERED));
this.walletName = walletName;
Panel mainPanel = new Panel();
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5).setVerticalSpacing(1));
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
mainPanel.addComponent(new Label("Script type"));
scriptType = new ComboBox<>();
mainPanel.addComponent(scriptType);
mainPanel.addComponent(new Label("Seed words"));
seedWords = new TextBox(new TerminalSize(MAX_COLUMNS, 5));
mainPanel.addComponent(seedWords);
mainPanel.addComponent(new Label("Passphrase"));
passphrase = new TextBox(new TerminalSize(25, 1));
mainPanel.addComponent(passphrase);
Panel buttonPanel = new Panel();
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
createWallet = new Button("Create Wallet", this::createWallet).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false));
createWallet.setEnabled(false);
buttonPanel.addComponent(createWallet);
mainPanel.addComponent(new Button("Generate New", () -> generateNew()));
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
setComponent(mainPanel);
ScriptType.getAddressableScriptTypes(PolicyType.SINGLE).stream().map(DisplayScriptType::new).forEach(scriptType::addItem);
scriptType.setSelectedItem(new DisplayScriptType(ScriptType.P2WPKH));
seedWords.setTextChangeListener((newText, changedByUserInteraction) -> {
try {
String[] words = newText.split("[ \n]");
importer.getKeystore(scriptType.getSelectedItem().scriptType.getDefaultDerivation(), Arrays.asList(words), passphrase.getText());
createWallet.setEnabled(true);
} catch(ImportException e) {
createWallet.setEnabled(false);
}
if(changedByUserInteraction) {
List<String> lines = splitString(newText, MAX_COLUMNS);
String splitText = lines.stream().reduce((s1, s2) -> s1 + "\n" + s2).get();
if(!newText.equals(splitText)) {
seedWords.setText(splitText);
TerminalPosition pos = seedWords.getCaretPosition();
if(pos.getRow() == lines.size() - 2 && pos.getColumn() == lines.get(lines.size() - 2).length()) {
seedWords.setCaretPosition(lines.size() - 1, lines.get(lines.size() - 1).length());
}
}
}
});
}
private void generateNew() {
WordNumberDialog wordNumberDialog = new WordNumberDialog();
Integer numberOfWords = wordNumberDialog.showDialog(SparrowTerminal.get().getGui());
if(numberOfWords != null) {
int mnemonicSeedLength = numberOfWords * 11;
int entropyLength = mnemonicSeedLength - (mnemonicSeedLength/33);
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstanceStrong();
} catch(NoSuchAlgorithmException e) {
secureRandom = new SecureRandom();
}
DeterministicSeed deterministicSeed = new DeterministicSeed(secureRandom, entropyLength, "");
List<String> words = deterministicSeed.getMnemonicCode();
setWords(words);
}
}
private List<String> getWords() {
return List.of(seedWords.getText().split("[ \n]"));
}
private void setWords(List<String> words) {
String text = words.stream().reduce((s1, s2) -> s1 + " " + s2).get();
List<String> splitText = splitString(text, MAX_COLUMNS);
seedWords.setText(splitText.stream().reduce((s1, s2) -> s1 + "\n" + s2).get());
}
public static List<String> splitString(String stringToSplit, int maxLength) {
int splitLength = maxLength - 1;
String text = stringToSplit;
List<String> lines = new ArrayList<>();
while (text.length() > splitLength) {
int spaceAt = splitLength - 1;
// the text is too long.
// find the last space before the maxLength
for (int i = splitLength - 1; i > 0; i--) {
if (Character.isWhitespace(text.charAt(i))) {
spaceAt = i;
break;
}
}
lines.add(text.substring(0, spaceAt));
text = text.substring(spaceAt + 1);
}
lines.add(text);
return lines;
}
private void createWallet() {
close();
try {
wallet = getWallet();
discoverAndSaveWallet(wallet);
} catch(ImportException e) {
log.error("Cannot import wallet", e);
}
}
private Wallet getWallet() throws ImportException {
Wallet wallet = new Wallet(walletName);
wallet.setPolicyType(PolicyType.SINGLE);
wallet.setScriptType(scriptType.getSelectedItem().scriptType);
Keystore keystore = importer.getKeystore(wallet.getScriptType().getDefaultDerivation(), getWords(), passphrase.getText());
wallet.getKeystores().add(keystore);
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, wallet.getScriptType(), wallet.getKeystores(), 1));
return wallet;
}
private void onCancel() {
close();
}
@Override
public Wallet showDialog(WindowBasedTextGUI textGUI) {
super.showDialog(textGUI);
return wallet;
}
private static final class DisplayScriptType {
private final ScriptType scriptType;
public DisplayScriptType(ScriptType scriptType) {
this.scriptType = scriptType;
}
@Override
public String toString() {
return scriptType.getDescription();
}
@Override
public boolean equals(Object o) {
if(this == o) {
return true;
}
if(o == null || getClass() != o.getClass()) {
return false;
}
DisplayScriptType that = (DisplayScriptType) o;
return scriptType == that.scriptType;
}
@Override
public int hashCode() {
return scriptType.hashCode();
}
}
private static final class WordNumberDialog extends DialogWindow {
ComboBox<Integer> wordCount;
private Integer numberOfWords;
public WordNumberDialog() {
super("Generate Seed Words");
setHints(List.of(Hint.CENTERED));
Panel mainPanel = new Panel();
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5).setVerticalSpacing(1));
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
mainPanel.addComponent(new Label("Number of words"));
wordCount = new ComboBox<>();
mainPanel.addComponent(wordCount);
wordCount.addItem(24);
wordCount.addItem(21);
wordCount.addItem(18);
wordCount.addItem(15);
wordCount.addItem(12);
Panel buttonPanel = new Panel();
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1));
buttonPanel.addComponent(new Button("Cancel", this::onCancel));
Button okButton = new Button("Ok", this::onOk).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false));
buttonPanel.addComponent(okButton);
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE));
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel);
setComponent(mainPanel);
}
private void onOk() {
numberOfWords = wordCount.getSelectedItem();
close();
}
private void onCancel() {
close();
}
@Override
public Integer showDialog(WindowBasedTextGUI textGUI) {
super.showDialog(textGUI);
return numberOfWords;
}
}
}

View file

@ -8,28 +8,20 @@ import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.SparrowWallet;
import com.sparrowwallet.sparrow.TabData;
import com.sparrowwallet.sparrow.WalletTabData;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.io.StorageException;
import com.sparrowwallet.sparrow.io.WalletAndKey;
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
import com.sparrowwallet.sparrow.wallet.WalletForm;
import javafx.application.Platform;
import javafx.scene.control.ButtonType;
import javafx.stage.Window;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS;
public class LoadWallet implements Runnable {
private static final Logger log = LoggerFactory.getLogger(LoadWallet.class);
@ -113,7 +105,7 @@ public class LoadWallet implements Runnable {
if(!walletAndKey.getWallet().isValid()) {
throw new IllegalStateException("Wallet file is not valid.");
}
addWallet(storage, walletAndKey.getWallet());
SparrowTerminal.addWallet(storage, walletAndKey.getWallet());
for(Map.Entry<WalletAndKey, Storage> entry : walletAndKey.getChildWallets().entrySet()) {
openWallet(entry.getValue(), entry.getKey());
}
@ -131,34 +123,6 @@ public class LoadWallet implements Runnable {
}
}
private void addWallet(Storage storage, Wallet wallet) {
if(wallet.isNested()) {
WalletData walletData = SparrowTerminal.get().getWalletData().get(storage.getWalletId(wallet.getMasterWallet()));
WalletForm walletForm = new WalletForm(storage, wallet);
EventManager.get().register(walletForm);
walletData.getWalletForm().getNestedWalletForms().add(walletForm);
} else {
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
WalletForm walletForm = new WalletForm(storage, wallet);
EventManager.get().register(walletForm);
SparrowTerminal.get().getWalletData().put(walletForm.getWalletId(), new WalletData(walletForm));
List<WalletTabData> walletTabDataList = SparrowTerminal.get().getWalletData().values().stream()
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList());
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList));
Set<File> walletFiles = new LinkedHashSet<>();
walletFiles.add(storage.getWalletFile());
if(Config.get().getRecentWalletFiles() != null) {
walletFiles.addAll(Config.get().getRecentWalletFiles().stream().limit(MAX_RECENT_WALLETS - 1).collect(Collectors.toList()));
}
Config.get().setRecentWalletFiles(Config.get().isLoadRecentWallets() ? new ArrayList<>(walletFiles) : Collections.emptyList());
}
EventManager.get().post(new WalletOpenedEvent(storage, wallet));
}
public static DialogWindow getOpeningDialog(Storage storage, Wallet masterWallet) {
if(masterWallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) {
return new WalletAccountsDialog(storage.getWalletId(masterWallet));
@ -167,8 +131,6 @@ public class LoadWallet implements Runnable {
}
}
private static final javafx.stage.Window DEFAULT_WINDOW = new Window() { };
private static final class LoadingDialog extends DialogWindow {
public LoadingDialog(Storage storage) {
super(storage.getWalletName(null));

View file

@ -0,0 +1,163 @@
package com.sparrowwallet.sparrow.terminal.wallet;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.gui2.EmptySpace;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder;
import com.sparrowwallet.drongo.SecureString;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.crypto.EncryptionType;
import com.sparrowwallet.drongo.crypto.Key;
import com.sparrowwallet.drongo.wallet.MnemonicException;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.StorageEvent;
import com.sparrowwallet.sparrow.event.TimedEvent;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.io.StorageException;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
public class NewWalletDialog extends DialogWindow {
private static final Logger log = LoggerFactory.getLogger(NewWalletDialog.class);
public NewWalletDialog(String title) {
super(title);
}
protected void discoverAndSaveWallet(Wallet wallet) {
if(AppServices.onlineProperty().get()) {
discoverAccounts(wallet);
} else {
saveWallet(wallet);
}
}
private void discoverAccounts(Wallet wallet) {
DiscoveringDialog discoveringDialog = new DiscoveringDialog(wallet);
SparrowTerminal.get().getGui().addWindow(discoveringDialog);
Platform.runLater(() -> {
ElectrumServer.WalletDiscoveryService walletDiscoveryService = new ElectrumServer.WalletDiscoveryService(List.of(wallet));
walletDiscoveryService.setOnSucceeded(successEvent -> {
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
SparrowTerminal.get().getGui().removeWindow(discoveringDialog);
saveWallet(wallet);
});
});
walletDiscoveryService.setOnFailed(failedEvent -> {
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
SparrowTerminal.get().getGui().removeWindow(discoveringDialog);
saveWallet(wallet);
});
log.error("Failed to discover accounts", failedEvent.getSource().getException());
});
walletDiscoveryService.start();
});
}
private void saveWallet(Wallet wallet) {
Storage storage = new Storage(Storage.getWalletFile(wallet.getName()));
TextInputDialogBuilder builder = new TextInputDialogBuilder().setTitle("Wallet Password");
builder.setDescription(SettingsDialog.PasswordRequirement.UPDATE_NEW.getDescription());
builder.setPasswordInput(true);
String password = builder.build().showDialog(SparrowTerminal.get().getGui());
if(password != null) {
Platform.runLater(() -> {
if(password.length() == 0) {
try {
storage.setEncryptionPubKey(Storage.NO_PASSWORD_KEY);
storage.saveWallet(wallet);
storage.restorePublicKeysFromSeed(wallet, null);
SparrowTerminal.addWallet(storage, wallet);
for(Wallet childWallet : wallet.getChildWallets()) {
storage.saveWallet(childWallet);
storage.restorePublicKeysFromSeed(childWallet, null);
SparrowTerminal.addWallet(storage, childWallet);
}
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, wallet).showDialog(SparrowTerminal.get().getGui()));
} catch(IOException | StorageException | MnemonicException e) {
log.error("Error saving imported wallet", e);
}
} else {
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(storage, new SecureString(password));
keyDerivationService.setOnSucceeded(workerStateEvent -> {
EventManager.get().post(new StorageEvent(Storage.getWalletFile(wallet.getName()).getAbsolutePath(), TimedEvent.Action.END, "Done"));
ECKey encryptionFullKey = keyDerivationService.getValue();
Key key = null;
try {
ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
key = new Key(encryptionFullKey.getPrivKeyBytes(), storage.getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
wallet.encrypt(key);
storage.setEncryptionPubKey(encryptionPubKey);
storage.saveWallet(wallet);
storage.restorePublicKeysFromSeed(wallet, key);
SparrowTerminal.addWallet(storage, wallet);
for(Wallet childWallet : wallet.getChildWallets()) {
if(!childWallet.isNested()) {
childWallet.encrypt(key);
}
storage.saveWallet(childWallet);
storage.restorePublicKeysFromSeed(childWallet, key);
SparrowTerminal.addWallet(storage, childWallet);
}
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, wallet).showDialog(SparrowTerminal.get().getGui()));
} catch(IOException | StorageException | MnemonicException e) {
log.error("Error saving imported wallet", e);
} finally {
encryptionFullKey.clear();
if(key != null) {
key.clear();
}
}
});
keyDerivationService.setOnFailed(workerStateEvent -> {
EventManager.get().post(new StorageEvent(Storage.getWalletFile(wallet.getName()).getAbsolutePath(), TimedEvent.Action.END, "Failed"));
showErrorDialog("Error encrypting wallet", keyDerivationService.getException().getMessage());
});
EventManager.get().post(new StorageEvent(Storage.getWalletFile(wallet.getName()).getAbsolutePath(), TimedEvent.Action.START, "Encrypting wallet..."));
keyDerivationService.start();
}
});
}
}
private static final class DiscoveringDialog extends DialogWindow {
public DiscoveringDialog(Wallet wallet) {
super(wallet.getName());
setHints(List.of(Hint.CENTERED));
setFixedSize(new TerminalSize(30, 5));
Panel mainPanel = new Panel();
mainPanel.setLayoutManager(new LinearLayout());
mainPanel.addComponent(new EmptySpace(), LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning, LinearLayout.GrowPolicy.CanGrow));
Label label = new Label("Discovering Accounts...");
mainPanel.addComponent(label, LinearLayout.createLayoutData(LinearLayout.Alignment.Center));
mainPanel.addComponent(new EmptySpace(), LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning, LinearLayout.GrowPolicy.CanGrow));
setComponent(mainPanel);
}
}
}

View file

@ -204,5 +204,9 @@ public class SettingsDialog extends WalletDialog {
this.description = description;
this.okButtonText = okButtonText;
}
public String getDescription() {
return description;
}
}
}