mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-04 21:36:45 +00:00
terminal - create bip39 wallet
This commit is contained in:
parent
8dd1850905
commit
8eb092a8d6
6 changed files with 540 additions and 72 deletions
|
@ -4,6 +4,7 @@ import com.googlecode.lanterna.TerminalSize;
|
||||||
import com.googlecode.lanterna.gui2.ActionListBox;
|
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||||
import com.googlecode.lanterna.gui2.dialogs.ActionListDialogBuilder;
|
import com.googlecode.lanterna.gui2.dialogs.ActionListDialogBuilder;
|
||||||
import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder;
|
import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder;
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
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.GeneralDialog;
|
||||||
import com.sparrowwallet.sparrow.terminal.preferences.ServerStatusDialog;
|
import com.sparrowwallet.sparrow.terminal.preferences.ServerStatusDialog;
|
||||||
import com.sparrowwallet.sparrow.terminal.preferences.ServerTypeDialog;
|
import com.sparrowwallet.sparrow.terminal.preferences.ServerTypeDialog;
|
||||||
|
import com.sparrowwallet.sparrow.terminal.wallet.Bip39Dialog;
|
||||||
import com.sparrowwallet.sparrow.terminal.wallet.LoadWallet;
|
import com.sparrowwallet.sparrow.terminal.wallet.LoadWallet;
|
||||||
import javafx.application.Platform;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -25,34 +26,32 @@ public class MasterActionListBox extends ActionListBox {
|
||||||
super(new TerminalSize(14, 3));
|
super(new TerminalSize(14, 3));
|
||||||
|
|
||||||
addItem("Wallets", () -> {
|
addItem("Wallets", () -> {
|
||||||
ActionListDialogBuilder builder = new ActionListDialogBuilder();
|
ActionListDialogBuilder builder = new ActionListDialogBuilder();
|
||||||
builder.setTitle("Wallets");
|
builder.setTitle("Wallets");
|
||||||
if(Config.get().getRecentWalletFiles() != null) {
|
if(Config.get().getRecentWalletFiles() != null) {
|
||||||
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) {
|
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) {
|
||||||
File recentWalletFile = Config.get().getRecentWalletFiles().get(i);
|
File recentWalletFile = Config.get().getRecentWalletFiles().get(i);
|
||||||
Storage storage = new Storage(recentWalletFile);
|
Storage storage = new Storage(recentWalletFile);
|
||||||
|
|
||||||
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream()
|
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream()
|
||||||
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey)
|
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey)
|
||||||
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst();
|
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst();
|
||||||
if(optWallet.isPresent()) {
|
if(optWallet.isPresent()) {
|
||||||
builder.addAction(storage.getWalletName(null) + "*", () -> LoadWallet.getOpeningDialog(storage, optWallet.get()).showDialog(SparrowTerminal.get().getGui()));
|
builder.addAction(storage.getWalletName(null) + "*", () -> {
|
||||||
} else {
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> LoadWallet.getOpeningDialog(storage, optWallet.get()).showDialog(SparrowTerminal.get().getGui()));
|
||||||
builder.addAction(storage.getWalletName(null), new LoadWallet(storage));
|
});
|
||||||
}
|
} else {
|
||||||
}
|
builder.addAction(storage.getWalletName(null), new LoadWallet(storage));
|
||||||
}
|
}
|
||||||
builder.addAction("Open Wallet...", () -> {
|
}
|
||||||
FileDialogBuilder openBuilder = new FileDialogBuilder().setTitle("Open Wallet");
|
}
|
||||||
openBuilder.setShowHiddenDirectories(true);
|
builder.addAction("Open Wallet...", () -> {
|
||||||
openBuilder.setSelectedFile(Storage.getWalletsDir());
|
SparrowTerminal.get().getGuiThread().invokeLater(MasterActionListBox::openWallet);
|
||||||
File file = openBuilder.build().showDialog(SparrowTerminal.get().getGui());
|
});
|
||||||
if(file != null) {
|
builder.addAction("Create Wallet...", () -> {
|
||||||
LoadWallet loadWallet = new LoadWallet(new Storage(file));
|
SparrowTerminal.get().getGuiThread().invokeLater(MasterActionListBox::createWallet);
|
||||||
SparrowTerminal.get().getGuiThread().invokeLater(loadWallet);
|
});
|
||||||
}
|
builder.build().showDialog(SparrowTerminal.get().getGui());
|
||||||
});
|
|
||||||
builder.build().showDialog(SparrowTerminal.get().getGui());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addItem("Preferences", () -> {
|
addItem("Preferences", () -> {
|
||||||
|
@ -77,4 +76,36 @@ public class MasterActionListBox extends ActionListBox {
|
||||||
|
|
||||||
addItem("Quit", () -> sparrowTerminal.getGui().getMainWindow().close());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,26 @@ import com.googlecode.lanterna.screen.TerminalScreen;
|
||||||
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
|
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
|
||||||
import com.googlecode.lanterna.terminal.Terminal;
|
import com.googlecode.lanterna.terminal.Terminal;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.*;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.event.OpenWalletsEvent;
|
||||||
import com.sparrowwallet.sparrow.SparrowWallet;
|
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.terminal.wallet.WalletData;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.io.File;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS;
|
||||||
|
|
||||||
public class SparrowTerminal extends Application {
|
public class SparrowTerminal extends Application {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SparrowTerminal.class);
|
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 final Map<String, WalletData> walletData = new HashMap<>();
|
||||||
|
|
||||||
|
private static final javafx.stage.Window DEFAULT_WINDOW = new Window() { };
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||||
|
@ -101,4 +111,32 @@ public class SparrowTerminal extends Application {
|
||||||
public static SparrowTerminal get() {
|
public static SparrowTerminal get() {
|
||||||
return sparrowTerminal;
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,28 +8,20 @@ import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.SparrowWallet;
|
import com.sparrowwallet.sparrow.SparrowWallet;
|
||||||
import com.sparrowwallet.sparrow.TabData;
|
|
||||||
import com.sparrowwallet.sparrow.WalletTabData;
|
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.io.StorageException;
|
import com.sparrowwallet.sparrow.io.StorageException;
|
||||||
import com.sparrowwallet.sparrow.io.WalletAndKey;
|
import com.sparrowwallet.sparrow.io.WalletAndKey;
|
||||||
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.stage.Window;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS;
|
|
||||||
|
|
||||||
public class LoadWallet implements Runnable {
|
public class LoadWallet implements Runnable {
|
||||||
private static final Logger log = LoggerFactory.getLogger(LoadWallet.class);
|
private static final Logger log = LoggerFactory.getLogger(LoadWallet.class);
|
||||||
|
@ -113,7 +105,7 @@ public class LoadWallet implements Runnable {
|
||||||
if(!walletAndKey.getWallet().isValid()) {
|
if(!walletAndKey.getWallet().isValid()) {
|
||||||
throw new IllegalStateException("Wallet file is not valid.");
|
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()) {
|
for(Map.Entry<WalletAndKey, Storage> entry : walletAndKey.getChildWallets().entrySet()) {
|
||||||
openWallet(entry.getValue(), entry.getKey());
|
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) {
|
public static DialogWindow getOpeningDialog(Storage storage, Wallet masterWallet) {
|
||||||
if(masterWallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) {
|
if(masterWallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) {
|
||||||
return new WalletAccountsDialog(storage.getWalletId(masterWallet));
|
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 {
|
private static final class LoadingDialog extends DialogWindow {
|
||||||
public LoadingDialog(Storage storage) {
|
public LoadingDialog(Storage storage) {
|
||||||
super(storage.getWalletName(null));
|
super(storage.getWalletName(null));
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -204,5 +204,9 @@ public class SettingsDialog extends WalletDialog {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.okButtonText = okButtonText;
|
this.okButtonText = okButtonText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue