mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 00:27:00 +00:00
show lifehash for master fingerprint in settings and passphrase dialog
This commit is contained in:
@@ -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<String> {
|
||||
private final CustomPasswordField passphrase;
|
||||
private final ObjectProperty<byte[]> masterFingerprint = new SimpleObjectProperty<>();
|
||||
|
||||
public KeystorePassphraseDialog(Keystore keystore) {
|
||||
this(null, keystore);
|
||||
@@ -45,10 +48,38 @@ public class KeystorePassphraseDialog extends Dialog<String> {
|
||||
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<String> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<byte[]> 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<byte[]> dataProperty() {
|
||||
return dataProperty;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
this.dataProperty.set(data);
|
||||
}
|
||||
|
||||
public void setHex(String hex) {
|
||||
setData(hex == null ? null : Utils.hexToBytes(hex));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user