mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
import and support master private key keystores
This commit is contained in:
parent
702d92d4f2
commit
3fc2127337
17 changed files with 366 additions and 35 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit 1aeaacaf59484c76d5bf485dabb4a632c5230032
|
||||
Subproject commit 85e8b97a8c8d21bfbb76096285eec95d28384090
|
|
@ -787,7 +787,7 @@ public class AppController implements Initializable {
|
|||
}
|
||||
|
||||
private void restorePublicKeysFromSeed(Wallet wallet, Key key) throws MnemonicException {
|
||||
if(wallet.containsSeeds()) {
|
||||
if(wallet.containsPrivateKeys()) {
|
||||
//Derive xpub and master fingerprint from seed, potentially with passphrase
|
||||
Wallet copy = wallet.copy();
|
||||
for(Keystore copyKeystore : copy.getKeystores()) {
|
||||
|
@ -823,6 +823,12 @@ public class AppController implements Initializable {
|
|||
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
|
||||
keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase());
|
||||
copyKeystore.getSeed().clear();
|
||||
} else if(keystore.hasMasterPrivateExtendedKey()) {
|
||||
Keystore copyKeystore = copy.getKeystores().get(i);
|
||||
Keystore derivedKeystore = Keystore.fromMasterPrivateExtendedKey(copyKeystore.getMasterPrivateExtendedKey(), copyKeystore.getKeyDerivation().getDerivation());
|
||||
keystore.setKeyDerivation(derivedKeystore.getKeyDerivation());
|
||||
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
|
||||
copyKeystore.getMasterPrivateKey().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -966,7 +972,7 @@ public class AppController implements Initializable {
|
|||
WalletTabData walletTabData = (WalletTabData)tab.getUserData();
|
||||
Wallet wallet = walletTabData.getWallet();
|
||||
if(wallet.getKeystores().size() == 1 &&
|
||||
(wallet.getKeystores().get(0).hasSeed() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB)) {
|
||||
(wallet.getKeystores().get(0).hasPrivateKey() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB)) {
|
||||
//Can sign and verify
|
||||
messageSignDialog = new MessageSignDialog(wallet);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> {
|
|||
actionBox.getChildren().add(receiveButton);
|
||||
|
||||
if(nodeEntry.getWallet().getKeystores().size() == 1 &&
|
||||
(nodeEntry.getWallet().getKeystores().get(0).hasSeed() || nodeEntry.getWallet().getKeystores().get(0).getSource() == KeystoreSource.HW_USB)) {
|
||||
(nodeEntry.getWallet().getKeystores().get(0).hasPrivateKey() || nodeEntry.getWallet().getKeystores().get(0).getSource() == KeystoreSource.HW_USB)) {
|
||||
Button signMessageButton = new Button("");
|
||||
Glyph signMessageGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.PEN_FANCY);
|
||||
signMessageGlyph.setFontSize(12);
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class MasterKeyDisplayDialog extends Dialog<Void> {
|
||||
public MasterKeyDisplayDialog(Keystore decryptedKeystore) {
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
dialogPane.setContent(stackPane);
|
||||
|
||||
AnchorPane anchorPane = new AnchorPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.getStyleClass().add("edge-to-edge");
|
||||
scrollPane.setPrefHeight(200);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
anchorPane.getChildren().add(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0.0);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0.0);
|
||||
|
||||
Accordion keystoreAccordion = new Accordion();
|
||||
scrollPane.setContent(keystoreAccordion);
|
||||
|
||||
XprvKeystoreImportPane keystorePane = new XprvKeystoreImportPane(decryptedKeystore);
|
||||
keystorePane.setAnimated(false);
|
||||
keystoreAccordion.getPanes().add(keystorePane);
|
||||
|
||||
stackPane.getChildren().addAll(anchorPane);
|
||||
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
dialogPane.getButtonTypes().addAll(cancelButtonType);
|
||||
|
||||
dialogPane.setPrefWidth(500);
|
||||
dialogPane.setPrefHeight(260);
|
||||
|
||||
Platform.runLater(() -> keystoreAccordion.setExpandedPane(keystorePane));
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
if(wallet.getKeystores().size() != 1) {
|
||||
throw new IllegalArgumentException("Cannot sign messages using a wallet with multiple keystores - a single key is required");
|
||||
}
|
||||
if(!wallet.getKeystores().get(0).hasSeed() && wallet.getKeystores().get(0).getSource() != KeystoreSource.HW_USB) {
|
||||
if(!wallet.getKeystores().get(0).hasPrivateKey() && wallet.getKeystores().get(0).getSource() != KeystoreSource.HW_USB) {
|
||||
throw new IllegalArgumentException("Cannot sign messages using a wallet without a seed or USB keystore");
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
return;
|
||||
}
|
||||
|
||||
if(wallet.containsSeeds()) {
|
||||
if(wallet.containsPrivateKeys()) {
|
||||
if(wallet.isEncrypted()) {
|
||||
EventManager.get().post(new RequestOpenWalletsEvent());
|
||||
} else {
|
||||
|
@ -230,6 +230,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
String signatureText = privKey.signMessage(message.getText().trim(), decryptedWallet.getScriptType(), null);
|
||||
signature.clear();
|
||||
signature.appendText(signatureText);
|
||||
privKey.clear();
|
||||
} catch(Exception e) {
|
||||
log.error("Could not sign message", e);
|
||||
AppServices.showErrorDialog("Could not sign message", e.getMessage());
|
||||
|
@ -316,6 +317,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Done"));
|
||||
Wallet decryptedWallet = decryptWalletService.getValue();
|
||||
signUnencryptedKeystore(decryptedWallet);
|
||||
decryptedWallet.clearPrivate();
|
||||
});
|
||||
decryptWalletService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Failed"));
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.drongo.ExtendedKey;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||
import com.sparrowwallet.sparrow.io.ImportException;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreXprvImport;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class XprvKeystoreImportPane extends TitledDescriptionPane {
|
||||
protected final Wallet wallet;
|
||||
protected final KeystoreXprvImport importer;
|
||||
|
||||
private Button enterXprvButton;
|
||||
private SplitMenuButton importButton;
|
||||
|
||||
private ExtendedKey xprv;
|
||||
|
||||
public XprvKeystoreImportPane(Wallet wallet, KeystoreXprvImport importer) {
|
||||
super(importer.getName(), "Extended key import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
||||
this.wallet = wallet;
|
||||
this.importer = importer;
|
||||
|
||||
createImportButton();
|
||||
buttonBox.getChildren().add(importButton);
|
||||
}
|
||||
|
||||
public XprvKeystoreImportPane(Keystore keystore) {
|
||||
super("Master Private Key", "BIP32 key", "", "image/" + WalletModel.SEED.getType() + ".png");
|
||||
this.wallet = null;
|
||||
this.importer = null;
|
||||
|
||||
try {
|
||||
this.xprv = keystore.getExtendedMasterPrivateKey();
|
||||
} catch(MnemonicException e) {
|
||||
//can't happen
|
||||
}
|
||||
|
||||
showHideLink.setVisible(false);
|
||||
buttonBox.getChildren().clear();
|
||||
setContent(getXprvEntry(true));
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createButton() {
|
||||
enterXprvButton = new Button("Enter Private Key");
|
||||
enterXprvButton.managedProperty().bind(enterXprvButton.visibleProperty());
|
||||
enterXprvButton.setOnAction(event -> {
|
||||
enterXprvButton.setDisable(true);
|
||||
enterXprv();
|
||||
});
|
||||
|
||||
return enterXprvButton;
|
||||
}
|
||||
|
||||
private void createImportButton() {
|
||||
importButton = new SplitMenuButton();
|
||||
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
importButton.setText("Import Keystore");
|
||||
importButton.getStyleClass().add("default-button");
|
||||
importButton.setOnAction(event -> {
|
||||
importButton.setDisable(true);
|
||||
importKeystore(wallet.getScriptType().getDefaultDerivation());
|
||||
});
|
||||
String[] accounts = new String[] {"Import Default Account #0", "Import Account #1", "Import Account #2", "Import Account #3", "Import Account #4", "Import Account #5", "Import Account #6", "Import Account #7", "Import Account #8", "Import Account #9"};
|
||||
int scriptAccountsLength = ScriptType.P2SH.equals(wallet.getScriptType()) ? 1 : accounts.length;
|
||||
for(int i = 0; i < scriptAccountsLength; i++) {
|
||||
MenuItem item = new MenuItem(accounts[i]);
|
||||
final List<ChildNumber> derivation = wallet.getScriptType().getDefaultDerivation(i);
|
||||
item.setOnAction(event -> {
|
||||
importButton.setDisable(true);
|
||||
importKeystore(derivation);
|
||||
});
|
||||
importButton.getItems().add(item);
|
||||
}
|
||||
|
||||
importButton.managedProperty().bind(importButton.visibleProperty());
|
||||
importButton.setVisible(false);
|
||||
}
|
||||
|
||||
private void enterXprv() {
|
||||
setDescription("Enter master private key");
|
||||
showHideLink.setVisible(false);
|
||||
setContent(getXprvEntry(false));
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
private void importKeystore(List<ChildNumber> derivation) {
|
||||
importButton.setDisable(true);
|
||||
try {
|
||||
Keystore keystore = importer.getKeystore(derivation, xprv);
|
||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||
} catch (ImportException e) {
|
||||
String errorMessage = e.getMessage();
|
||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||
errorMessage = e.getCause().getMessage();
|
||||
}
|
||||
setError("Import Error", errorMessage);
|
||||
importButton.setDisable(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Node getXprvEntry(boolean displayOnly) {
|
||||
TextArea xprvField = new TextArea();
|
||||
xprvField.setPrefRowCount(2);
|
||||
xprvField.setWrapText(true);
|
||||
xprvField.getStyleClass().add("fixed-width");
|
||||
xprvField.setPromptText(ExtendedKey.Header.fromScriptType(ScriptType.P2PKH, true).getName() + (wallet != null ? "/" + ExtendedKey.Header.fromScriptType(wallet.getScriptType(), true).getName() : "") + "...");
|
||||
HBox.setHgrow(xprvField, Priority.ALWAYS);
|
||||
|
||||
if(xprv != null) {
|
||||
xprvField.setText(xprv.toString());
|
||||
}
|
||||
if(displayOnly) {
|
||||
xprvField.setEditable(false);
|
||||
}
|
||||
|
||||
ValidationSupport validationSupport = new ValidationSupport();
|
||||
validationSupport.registerValidator(xprvField, Validator.combine(
|
||||
Validator.createEmptyValidator("xprv is required"),
|
||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid private key", !ExtendedKey.isValid(newValue) || ExtendedKey.fromDescriptor(newValue).getKey().isPubKeyOnly())
|
||||
));
|
||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||
|
||||
Button importXprvButton = new Button("Import");
|
||||
importXprvButton.setMinWidth(80);
|
||||
importXprvButton.setDisable(true);
|
||||
importXprvButton.setOnAction(event -> {
|
||||
enterXprvButton.setVisible(false);
|
||||
importButton.setVisible(true);
|
||||
setDescription("Ready to import");
|
||||
xprv = ExtendedKey.fromDescriptor(xprvField.getText());
|
||||
setContent(getDerivationEntry(wallet.getScriptType().getDefaultDerivation()));
|
||||
});
|
||||
|
||||
xprvField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
importXprvButton.setDisable(newValue.isEmpty() || !ExtendedKey.isValid(newValue) || ExtendedKey.fromDescriptor(newValue).getKey().isPubKeyOnly());
|
||||
});
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||
contentBox.setSpacing(20);
|
||||
contentBox.getChildren().add(xprvField);
|
||||
if(!displayOnly) {
|
||||
contentBox.getChildren().add(importXprvButton);
|
||||
}
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
contentBox.setPrefHeight(100);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
private Node getDerivationEntry(List<ChildNumber> derivation) {
|
||||
TextField derivationField = new TextField();
|
||||
derivationField.setPromptText("Derivation path");
|
||||
derivationField.setText(KeyDerivation.writePath(derivation));
|
||||
HBox.setHgrow(derivationField, Priority.ALWAYS);
|
||||
|
||||
ValidationSupport validationSupport = new ValidationSupport();
|
||||
validationSupport.registerValidator(derivationField, Validator.combine(
|
||||
Validator.createEmptyValidator("Derivation is required"),
|
||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid derivation", !KeyDerivation.isValid(newValue))
|
||||
));
|
||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||
|
||||
Button importDerivationButton = new Button("Import Custom Derivation Keystore");
|
||||
importDerivationButton.setDisable(true);
|
||||
importDerivationButton.setOnAction(event -> {
|
||||
showHideLink.setVisible(true);
|
||||
setExpanded(false);
|
||||
List<ChildNumber> importDerivation = KeyDerivation.parsePath(derivationField.getText());
|
||||
importKeystore(importDerivation);
|
||||
});
|
||||
|
||||
derivationField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
importButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue) || !KeyDerivation.parsePath(newValue).equals(derivation));
|
||||
importDerivationButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue) || KeyDerivation.parsePath(newValue).equals(derivation));
|
||||
});
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||
contentBox.setSpacing(20);
|
||||
contentBox.getChildren().add(derivationField);
|
||||
contentBox.getChildren().add(importDerivationButton);
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
contentBox.setPrefHeight(60);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
}
|
36
src/main/java/com/sparrowwallet/sparrow/io/Bip32.java
Normal file
36
src/main/java/com/sparrowwallet/sparrow/io/Bip32.java
Normal file
|
@ -0,0 +1,36 @@
|
|||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import com.sparrowwallet.drongo.ExtendedKey;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.MasterPrivateExtendedKey;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Bip32 implements KeystoreXprvImport {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Master Private Key (BIP32)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public WalletModel getWalletModel() {
|
||||
return WalletModel.SEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeystoreImportDescription() {
|
||||
return "Import an extended master private key (BIP 32 xprv)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keystore getKeystore(List<ChildNumber> derivation, ExtendedKey xprv) throws ImportException {
|
||||
try {
|
||||
MasterPrivateExtendedKey masterPrivateExtendedKey = new MasterPrivateExtendedKey(xprv.getKey().getPrivKeyBytes(), xprv.getKey().getChainCode());
|
||||
return Keystore.fromMasterPrivateExtendedKey(masterPrivateExtendedKey, derivation);
|
||||
} catch(Exception e) {
|
||||
throw new ImportException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -328,11 +328,11 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
|||
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
||||
ek.xprv = keystore.getExtendedPrivateKey().toString(xprvHeader);
|
||||
ek.pw_hash_version = 1;
|
||||
if(keystore.getSeed().getType() == DeterministicSeed.Type.ELECTRUM) {
|
||||
if(keystore.getSeed() == null || keystore.getSeed().getType() == DeterministicSeed.Type.BIP39) {
|
||||
ew.seed_type = "bip39";
|
||||
} else if(keystore.getSeed().getType() == DeterministicSeed.Type.ELECTRUM) {
|
||||
ek.seed = keystore.getSeed().getMnemonicString().asString();
|
||||
ek.passphrase = keystore.getSeed().getPassphrase() == null ? null : keystore.getSeed().getPassphrase().asString();
|
||||
} else if(keystore.getSeed().getType() == DeterministicSeed.Type.BIP39) {
|
||||
ew.seed_type = "bip39";
|
||||
}
|
||||
ew.use_encryption = false;
|
||||
} else if(keystore.getSource() == KeystoreSource.SW_WATCH) {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import com.sparrowwallet.drongo.ExtendedKey;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface KeystoreXprvImport extends KeystoreImport {
|
||||
Keystore getKeystore(List<ChildNumber> derivation, ExtendedKey xprv) throws ImportException;
|
||||
}
|
|
@ -527,7 +527,7 @@ public class Storage {
|
|||
@Override
|
||||
public JsonElement serialize(Keystore keystore, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = (JsonObject)getGson(false).toJsonTree(keystore);
|
||||
if(keystore.hasSeed()) {
|
||||
if(keystore.hasPrivateKey()) {
|
||||
jsonObject.remove("extendedPublicKey");
|
||||
jsonObject.getAsJsonObject("keyDerivation").remove("masterFingerprint");
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.keystoreimport;
|
|||
import com.sparrowwallet.sparrow.control.FileKeystoreImportPane;
|
||||
import com.sparrowwallet.sparrow.control.MnemonicKeystoreImportPane;
|
||||
import com.sparrowwallet.sparrow.control.TitledDescriptionPane;
|
||||
import com.sparrowwallet.sparrow.control.XprvKeystoreImportPane;
|
||||
import com.sparrowwallet.sparrow.io.*;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Accordion;
|
||||
|
@ -14,7 +15,7 @@ public class SwController extends KeystoreImportDetailController {
|
|||
private Accordion importAccordion;
|
||||
|
||||
public void initializeView() {
|
||||
List<KeystoreImport> importers = List.of(new Bip39(), new Electrum());
|
||||
List<KeystoreImport> importers = List.of(new Bip39(), new Electrum(), new Bip32());
|
||||
|
||||
for(KeystoreImport importer : importers) {
|
||||
TitledDescriptionPane importPane = null;
|
||||
|
@ -23,6 +24,8 @@ public class SwController extends KeystoreImportDetailController {
|
|||
importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer);
|
||||
} else if(importer instanceof KeystoreMnemonicImport) {
|
||||
importPane = new MnemonicKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicImport)importer);
|
||||
} else if(importer instanceof KeystoreXprvImport) {
|
||||
importPane = new XprvKeystoreImportPane(getMasterController().getWallet(), (KeystoreXprvImport)importer);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
|
||||
}
|
||||
|
|
|
@ -698,7 +698,7 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
}
|
||||
|
||||
private void signSoftwareKeystores() {
|
||||
if(headersForm.getSigningWallet().getKeystores().stream().noneMatch(Keystore::hasSeed)) {
|
||||
if(headersForm.getSigningWallet().getKeystores().stream().noneMatch(Keystore::hasPrivateKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,7 @@ import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
|||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.QRDisplayDialog;
|
||||
import com.sparrowwallet.sparrow.control.QRScanDialog;
|
||||
import com.sparrowwallet.sparrow.control.SeedDisplayDialog;
|
||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||
import com.sparrowwallet.sparrow.control.*;
|
||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
|
@ -57,6 +54,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
@FXML
|
||||
private Button viewSeedButton;
|
||||
|
||||
@FXML
|
||||
private Button viewKeyButton;
|
||||
|
||||
@FXML
|
||||
private Button importButton;
|
||||
|
||||
|
@ -106,6 +106,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
}
|
||||
|
||||
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
||||
viewKeyButton.managedProperty().bind(viewKeyButton.visibleProperty());
|
||||
scanXpubQR.managedProperty().bind(scanXpubQR.visibleProperty());
|
||||
displayXpubQR.managedProperty().bind(displayXpubQR.visibleProperty());
|
||||
displayXpubQR.visibleProperty().bind(scanXpubQR.visibleProperty().not());
|
||||
|
@ -246,7 +247,8 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
private void updateType() {
|
||||
type.setText(getTypeLabel(keystore));
|
||||
type.setGraphic(getTypeIcon(keystore));
|
||||
viewSeedButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED);
|
||||
viewSeedButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasSeed());
|
||||
viewKeyButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasMasterPrivateExtendedKey());
|
||||
|
||||
importButton.setText(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import..." : "Replace...");
|
||||
importButton.setTooltip(new Tooltip(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import a keystore from an external source" : "Replace this keystore with another source"));
|
||||
|
@ -317,6 +319,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
keystore.setLabel(importedKeystore.getLabel());
|
||||
keystore.setKeyDerivation(importedKeystore.getKeyDerivation());
|
||||
keystore.setExtendedPublicKey(importedKeystore.getExtendedPublicKey());
|
||||
keystore.setMasterPrivateExtendedKey(importedKeystore.getMasterPrivateExtendedKey());
|
||||
keystore.setSeed(importedKeystore.getSeed());
|
||||
|
||||
updateType();
|
||||
|
@ -333,7 +336,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
}
|
||||
}
|
||||
|
||||
public void showSeed(ActionEvent event) {
|
||||
public void showPrivate(ActionEvent event) {
|
||||
int keystoreIndex = getWalletForm().getWallet().getKeystores().indexOf(keystore);
|
||||
Wallet copy = getWalletForm().getWallet().copy();
|
||||
|
||||
|
@ -345,7 +348,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
decryptWalletService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(getWalletForm().getWalletFile(), TimedEvent.Action.END, "Done"));
|
||||
Wallet decryptedWallet = decryptWalletService.getValue();
|
||||
showSeed(decryptedWallet.getKeystores().get(keystoreIndex));
|
||||
showPrivate(decryptedWallet.getKeystores().get(keystoreIndex));
|
||||
});
|
||||
decryptWalletService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(getWalletForm().getWalletFile(), TimedEvent.Action.END, "Failed"));
|
||||
|
@ -355,13 +358,18 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
decryptWalletService.start();
|
||||
}
|
||||
} else {
|
||||
showSeed(keystore);
|
||||
showPrivate(keystore);
|
||||
}
|
||||
}
|
||||
|
||||
private void showSeed(Keystore keystore) {
|
||||
private void showPrivate(Keystore keystore) {
|
||||
if(keystore.hasSeed()) {
|
||||
SeedDisplayDialog dlg = new SeedDisplayDialog(keystore);
|
||||
dlg.showAndWait();
|
||||
} else if(keystore.hasMasterPrivateExtendedKey()) {
|
||||
MasterKeyDisplayDialog dlg = new MasterKeyDisplayDialog(keystore);
|
||||
dlg.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
public void scanXpubQR(ActionEvent event) {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<Fieldset inputGrow="SOMETIMES" text="">
|
||||
<Field text="Type:">
|
||||
<Label fx:id="type" graphicTextGap="8"/>
|
||||
<Button fx:id="viewSeedButton" text="View Seed..." graphicTextGap="5" onAction="#showSeed">
|
||||
<Button fx:id="viewSeedButton" text="View Seed..." graphicTextGap="5" onAction="#showPrivate">
|
||||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="KEY" />
|
||||
</graphic>
|
||||
|
@ -30,6 +30,14 @@
|
|||
<Tooltip text="View mnemonic seed words"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button fx:id="viewKeyButton" text="View Key..." graphicTextGap="5" onAction="#showPrivate">
|
||||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="KEY" />
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="View master private key"/>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Pane HBox.hgrow="ALWAYS" />
|
||||
<Button fx:id="importButton" text="Import..." graphicTextGap="5" onAction="#importKeystore">
|
||||
<graphic>
|
||||
|
|
|
@ -64,7 +64,7 @@ public class ColdcardMultisigTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType());
|
||||
Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("wsh(sortedmulti(2,coldcard1,coldcard2,coldcard3,coldcard4))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("wsh(sortedmulti(2,coldcard1,coldcard2,coldcard3,coldcard4))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertTrue(wallet.isValid());
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class ColdcardMultisigTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType());
|
||||
Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("sh(wsh(sortedmulti(2,coldcard1,coldcard2,coldcard3,coldcard4)))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("sh(wsh(sortedmulti(2,coldcard1,coldcard2,coldcard3,coldcard4)))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertTrue(wallet.isValid());
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ public class ColdcardMultisigTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType());
|
||||
Assert.assertEquals(3, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("wsh(sortedmulti(3,coldcard1,coldcard2,coldcard3))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("wsh(sortedmulti(3,coldcard1,coldcard2,coldcard3))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("06b57041", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/48'/0'/0'/2'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub6EfEGa5isJbQFSswM5Uptw5BSq2Td1ZDJr3QUNUcMySpC7itZ3ccypVHtLPnvMzKQ2qxrAgH49vhVxRcaQLFbixAVRR8RACrYTp88Uv9h8Z", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
|
|
@ -24,7 +24,7 @@ public class ElectrumTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType());
|
||||
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("sh(wpkh(trezortest))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("sh(wpkh(trezortest))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("ab543c67", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/49'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub6FFEQVG6QR28chQzgSJ7Gjx5j5BGLkCMgZ9bc41YJCXfwYiCKUQdcwm4Fe1stvzRjosz5udMedYZFRL56AeZXCsiVmnVUysio4jkAKTukmN", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
@ -44,7 +44,7 @@ public class ElectrumTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType());
|
||||
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("sh(wpkh(trezortest))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("sh(wpkh(trezortest))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("ab543c67", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/49'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub6FFEQVG6QR28chQzgSJ7Gjx5j5BGLkCMgZ9bc41YJCXfwYiCKUQdcwm4Fe1stvzRjosz5udMedYZFRL56AeZXCsiVmnVUysio4jkAKTukmN", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
@ -59,7 +59,7 @@ public class ElectrumTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType());
|
||||
Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("sh(wsh(sortedmulti(2,coldcard6ba6cfd,coldcard747b698,coldcard7bb026b,coldcard0f05694)))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("sh(wsh(sortedmulti(2,coldcard6ba6cfd,coldcard747b698,coldcard7bb026b,coldcard0f05694)))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("6ba6cfd0", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/48'/1'/0'/1'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
@ -82,7 +82,7 @@ public class ElectrumTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType());
|
||||
Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("sh(wsh(sortedmulti(2,coldcard6ba6cfd,coldcard747b698,coldcard7bb026b,coldcard0f05694)))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("sh(wsh(sortedmulti(2,coldcard6ba6cfd,coldcard747b698,coldcard7bb026b,coldcard0f05694)))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("6ba6cfd0", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/48'/1'/0'/1'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
@ -100,7 +100,7 @@ public class ElectrumTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType());
|
||||
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("wpkh(electrum)", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("wpkh(electrum)", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("f881eac5", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub69iSRreMB6fu24sU8Tdxv7yYGqzPkDwPkwqUfKJTxW3p8afW7XvTewVCapuX3dQjdD197iF65WcjYaNpFbwWT3RyuZ1KJ3ToJNVWKWyAJ6f", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
@ -120,7 +120,7 @@ public class ElectrumTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType());
|
||||
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("wpkh(electrum)", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("wpkh(electrum)", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("59c5474f", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub68YmVxWbxqjpxbUqqaPrgkBQPBSJuq6gEaL22uuytSEojtS2x5eLPN2uspUuyigtnMkoHrFSF1KwoXPwjzuaUjErUwztxfHquAwuaQhSd9J", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
|
|
@ -15,7 +15,7 @@ public class SpecterDesktopTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType());
|
||||
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("sh(wpkh(keystore1))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("sh(wpkh(keystore1))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("4df18faa", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/49'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub6BgwyseZdeGJj2vB3FPHSGPxR1LLkr8AsAJqedrgjwBXKXXVWkH31fhwtQXgrM7uMrWjLwXhuDhhenNAh5eBdUSjrHkrKfaXutcJdAfgQ8D", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
@ -30,7 +30,7 @@ public class SpecterDesktopTest extends IoTest {
|
|||
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
|
||||
Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType());
|
||||
Assert.assertEquals(3, wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||
Assert.assertEquals("wsh(sortedmulti(3,keystore1,keystore2,keystore3,keystore4))", wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||
Assert.assertEquals("wsh(sortedmulti(3,keystore1,keystore2,keystore3,keystore4))", wallet.getDefaultPolicy().getMiniscript().getScript().toLowerCase());
|
||||
Assert.assertEquals("ca9a2b19", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
Assert.assertEquals("m/48'/0'/0'/2'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
|
||||
Assert.assertEquals("xpub6EhbRDNhmMX863W8RujJyAMw1vtM4MHXnsk14paK1ZBEH75k44gWqfaraXCrzg6w9pzC2yLc28vAdUfpB9ShuEB1HA9xMs6BjmRi4PKbt1K", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
|
||||
|
|
Loading…
Reference in a new issue