From ecf9b787532bc003adc979f7161d0aef3c8eaeba Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 19 Jul 2022 13:22:20 +0200 Subject: [PATCH] implement sweeping of funds from a bip38 encrypted private key --- drongo | 2 +- .../control/PrivateKeySweepDialog.java | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/drongo b/drongo index 5de3abd3..f1068827 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 5de3abd36230d545f11bad3b25a21d23ffbbe9cd +Subproject commit f10688279a7a761c734c80c051237c0a4a90928b diff --git a/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java index c810d030..5cce1fbc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/PrivateKeySweepDialog.java @@ -4,6 +4,7 @@ import com.google.common.io.Files; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.InvalidAddressException; +import com.sparrowwallet.drongo.crypto.BIP38; import com.sparrowwallet.drongo.crypto.DumpedPrivateKey; import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.policy.PolicyType; @@ -27,6 +28,7 @@ import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.StringConverter; +import org.controlsfx.control.textfield.CustomPasswordField; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.validation.ValidationResult; import org.controlsfx.validation.ValidationSupport; @@ -149,6 +151,10 @@ public class PrivateKeySweepDialog extends Dialog { }); key.textProperty().addListener((observable, oldValue, newValue) -> { + if(isEncryptedKey()) { + decryptKey(); + } + boolean isValidKey = isValidKey(); createButton.setDisable(!isValidKey || !isValidToAddress()); if(isValidKey) { @@ -199,6 +205,27 @@ public class PrivateKeySweepDialog extends Dialog { } } + private boolean isEncryptedKey() { + return key.getText().length() == 58 && key.getText().startsWith("6P"); + } + + private void decryptKey() { + PassphraseDialog passphraseDialog = new PassphraseDialog(); + Optional optPassphrase = passphraseDialog.showAndWait(); + if(optPassphrase.isPresent()) { + try { + DumpedPrivateKey decryptedKey = BIP38.decrypt(optPassphrase.get(), key.getText()); + Platform.runLater(() -> key.setText(decryptedKey.toString())); + } catch(Exception e) { + log.error("Failed to decrypt BIP38 key", e); + AppServices.showErrorDialog("Failed to decrypt BIP38 key", e.getMessage()); + Platform.runLater(() -> key.setText("")); + } + } else { + Platform.runLater(() -> key.setText("")); + } + } + private DumpedPrivateKey getPrivateKey() { return DumpedPrivateKey.fromBase58(key.getText()); } @@ -342,4 +369,35 @@ public class PrivateKeySweepDialog extends Dialog { glyph.setFontSize(12); return glyph; } + + public class PassphraseDialog extends Dialog { + private final CustomPasswordField passphrase; + + public PassphraseDialog() { + this.passphrase = new ViewPasswordField(); + + final DialogPane dialogPane = getDialogPane(); + setTitle("BIP38 Passphrase"); + dialogPane.setHeaderText("Enter the BIP38 passphrase for this key:"); + dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm()); + AppServices.setStageIcon(dialogPane.getScene().getWindow()); + dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK); + dialogPane.setPrefWidth(380); + dialogPane.setPrefHeight(200); + AppServices.moveToActiveWindowScreen(this); + + Glyph key = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.KEY); + key.setFontSize(50); + dialogPane.setGraphic(key); + + final VBox content = new VBox(10); + content.setPrefHeight(50); + content.getChildren().add(passphrase); + + dialogPane.setContent(content); + Platform.runLater(passphrase::requestFocus); + + setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? passphrase.getText() : null); + } + } }