From 803829848599d69b41921c94f5ae8d0293744c8a Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 11 Jan 2023 14:01:41 +0200 Subject: [PATCH] show lifehash for master fingerprint in settings and passphrase dialog --- .../control/KeystorePassphraseDialog.java | 53 ++++++++++++-- .../sparrow/control/LifeHashIcon.java | 70 +++++++++++++++++++ .../sparrowwallet/sparrow/io/ImageUtils.java | 8 +++ .../sparrow/wallet/KeystoreController.java | 12 ++++ src/main/java/module-info.java | 1 + .../sparrow/wallet/keystore.fxml | 4 +- 6 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/LifeHashIcon.java diff --git a/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java index 4f918d2f..a25a4315 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java @@ -1,20 +1,23 @@ package com.sparrowwallet.sparrow.control; +import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.MnemonicException; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import javafx.application.Platform; -import javafx.scene.control.ButtonType; -import javafx.scene.control.Dialog; -import javafx.scene.control.DialogPane; -import javafx.scene.control.Label; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import org.controlsfx.control.textfield.CustomPasswordField; -import org.controlsfx.control.textfield.TextFields; import org.controlsfx.glyphfont.Glyph; public class KeystorePassphraseDialog extends Dialog { private final CustomPasswordField passphrase; + private final ObjectProperty masterFingerprint = new SimpleObjectProperty<>(); public KeystorePassphraseDialog(Keystore keystore) { this(null, keystore); @@ -45,10 +48,38 @@ public class KeystorePassphraseDialog extends Dialog { content.setPrefHeight(50); content.getChildren().add(passphrase); + passphrase.textProperty().addListener((observable, oldValue, passphrase) -> { + masterFingerprint.set(getMasterFingerprint(keystore, passphrase)); + }); + + HBox fingerprintBox = new HBox(10); + fingerprintBox.setAlignment(Pos.CENTER_LEFT); + Label fingerprintLabel = new Label("Master fingerprint:"); + TextField fingerprintHex = new TextField(); + fingerprintHex.setDisable(true); + fingerprintHex.setPrefWidth(90); + fingerprintHex.getStyleClass().addAll("fixed-width"); + fingerprintHex.setStyle("-fx-opacity: 0.6"); + masterFingerprint.addListener((observable, oldValue, newValue) -> { + if(newValue != null) { + fingerprintHex.setText(Utils.bytesToHex(newValue)); + } + }); + LifeHashIcon lifeHashIcon = new LifeHashIcon(); + lifeHashIcon.dataProperty().bind(masterFingerprint); + HelpLabel helpLabel = new HelpLabel(); + helpLabel.setHelpText("All passphrases create valid wallets." + + "\nThe master fingerprint identifies the keystore and changes as the passphrase changes." + + "\n" + (confirm ? "Take a moment to identify it before proceeding." : "Make sure you recognise it before proceeding.")); + fingerprintBox.getChildren().addAll(fingerprintLabel, fingerprintHex, lifeHashIcon, helpLabel); + content.getChildren().add(fingerprintBox); + + masterFingerprint.set(getMasterFingerprint(keystore, "")); + Glyph warnGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE); warnGlyph.getStyleClass().add("warn-icon"); warnGlyph.setFontSize(12); - Label warnLabel = new Label("A BIP39 passphrase is not a wallet password!", warnGlyph); + Label warnLabel = new Label((confirm ? "Note" : "Check") + " the master fingerprint before proceeding!", warnGlyph); warnLabel.setGraphicTextGap(5); content.getChildren().add(warnLabel); @@ -57,4 +88,14 @@ public class KeystorePassphraseDialog extends Dialog { setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? passphrase.getText() : null); } + + private byte[] getMasterFingerprint(Keystore keystore, String passphrase) { + try { + Keystore copyKeystore = keystore.copy(); + copyKeystore.getSeed().setPassphrase(passphrase); + return copyKeystore.getExtendedMasterPrivateKey().getKey().getFingerprint(); + } catch(MnemonicException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/LifeHashIcon.java b/src/main/java/com/sparrowwallet/sparrow/control/LifeHashIcon.java new file mode 100644 index 00000000..1c5ab910 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/LifeHashIcon.java @@ -0,0 +1,70 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.sparrow.io.ImageUtils; +import com.sparrowwallet.toucan.LifeHash; +import com.sparrowwallet.toucan.LifeHashVersion; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.Group; +import javafx.scene.image.Image; +import javafx.scene.paint.Color; +import javafx.scene.paint.ImagePattern; +import javafx.scene.shape.Rectangle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.image.BufferedImage; +import java.util.Arrays; + +public class LifeHashIcon extends Group { + private static final Logger log = LoggerFactory.getLogger(LifeHashIcon.class); + + private static final int SIZE = 24; + + private final ObjectProperty dataProperty = new SimpleObjectProperty<>(null); + + public LifeHashIcon() { + super(); + + dataProperty.addListener((observable, oldValue, data) -> { + if(data == null) { + getChildren().clear(); + } else if(oldValue == null || !Arrays.equals(oldValue, data)) { + LifeHash.Image lifeHashImage = LifeHash.makeFromData(data, LifeHashVersion.VERSION2, 1, false); + BufferedImage bufferedImage = LifeHash.getBufferedImage(lifeHashImage); + BufferedImage resizedImage = ImageUtils.resizeToImage(bufferedImage, SIZE, SIZE); + Image image = SwingFXUtils.toFXImage(resizedImage, null); + setImage(image); + } + }); + } + + private void setImage(Image image) { + getChildren().clear(); + Rectangle rectangle = new Rectangle(SIZE, SIZE); + rectangle.setArcWidth(6); + rectangle.setArcHeight(6); + rectangle.setFill(new ImagePattern(image)); + rectangle.setStroke(Color.rgb(65, 72, 77)); + rectangle.setStrokeWidth(1.0); + getChildren().add(rectangle); + } + + public byte[] getData() { + return dataProperty.get(); + } + + public ObjectProperty dataProperty() { + return dataProperty; + } + + public void setData(byte[] data) { + this.dataProperty.set(data); + } + + public void setHex(String hex) { + setData(hex == null ? null : Utils.hexToBytes(hex)); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ImageUtils.java b/src/main/java/com/sparrowwallet/sparrow/io/ImageUtils.java index e85c9537..c88159da 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ImageUtils.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ImageUtils.java @@ -26,6 +26,14 @@ public class ImageUtils { resize(Thumbnails.of(image), outputStream, width, height); } + public static BufferedImage resizeToImage(BufferedImage image, int width, int height) { + try { + return Thumbnails.of(image).size(width, height).outputQuality(1).asBufferedImage(); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + public static byte[] resize(File file, int width, int height) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); resize(file, baos, width, height); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java index eab9d4b9..ccda0146 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java @@ -73,6 +73,9 @@ public class KeystoreController extends WalletFormController implements Initiali @FXML private TextField fingerprint; + @FXML + private LifeHashIcon fingerprintIcon; + @FXML private Button scanXpubQR; @@ -134,6 +137,7 @@ public class KeystoreController extends WalletFormController implements Initiali if(keystore.getKeyDerivation() != null) { derivation.setText(keystore.getKeyDerivation().getDerivationPath()); fingerprint.setText(keystore.getKeyDerivation().getMasterFingerprint()); + fingerprintIcon.setHex(fingerprint.getText()); } else { keystore.setKeyDerivation(new KeyDerivation("","")); } @@ -144,8 +148,16 @@ public class KeystoreController extends WalletFormController implements Initiali }); fingerprint.textProperty().addListener((observable, oldValue, newValue) -> { keystore.setKeyDerivation(new KeyDerivation(newValue, keystore.getKeyDerivation().getDerivationPath())); + fingerprintIcon.setHex(newValue.length() == 8 ? newValue : null); EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_FINGERPRINT)); }); + fingerprint.setTextFormatter(new TextFormatter<>(change -> { + String input = change.getText(); + if(input.matches("[0-9a-fA-F]*")) { + return change; + } + return null; + })); derivation.textProperty().addListener((observable, oldValue, newValue) -> { if(KeyDerivation.isValid(newValue) && !walletForm.getWallet().derivationMatchesAnotherScriptType(newValue)) { keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue)); diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 0d32b3cc..fc404818 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -48,4 +48,5 @@ open module com.sparrowwallet.sparrow { requires com.googlecode.lanterna; requires net.coobird.thumbnailator; requires com.github.hervegirod; + requires com.sparrowwallet.toucan; } \ No newline at end of file diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.fxml index 04e383fe..919cc19a 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/keystore.fxml @@ -14,6 +14,8 @@ + + @@ -49,7 +51,7 @@ - +