diff --git a/desktop/src/main/java/haveno/desktop/main/account/content/seedwords/SeedWordsView.java b/desktop/src/main/java/haveno/desktop/main/account/content/seedwords/SeedWordsView.java
index 9d90994029..1568c5ddde 100644
--- a/desktop/src/main/java/haveno/desktop/main/account/content/seedwords/SeedWordsView.java
+++ b/desktop/src/main/java/haveno/desktop/main/account/content/seedwords/SeedWordsView.java
@@ -1,36 +1,36 @@
/*
- * This file is part of Bisq.
- *
- * Bisq is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * Bisq is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Bisq. If not, see .
- */
+* This file is part of Bisq.
+*
+* Bisq is free software: you can redistribute it and/or modify it
+* under the terms of the GNU Affero General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or (at
+* your option) any later version.
+*
+* Bisq is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+* License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with Bisq. If not, see .
+*/
/*
- * This file is part of Haveno.
- *
- * Haveno is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * Haveno is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Haveno. If not, see .
- */
+* This file is part of Haveno.
+*
+* Haveno is free software: you can redistribute it and/or modify it
+* under the terms of the GNU Affero General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or (at
+* your option) any later version.
+*
+* Haveno is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+* License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with Haveno. If not, see .
+*/
package haveno.desktop.main.account.content.seedwords;
@@ -53,6 +53,7 @@ import static haveno.desktop.util.FormBuilder.addTitledGroupBg;
import static haveno.desktop.util.FormBuilder.addTopLabelDatePicker;
import static haveno.desktop.util.FormBuilder.addTopLabelTextArea;
import haveno.desktop.util.Layout;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
@@ -61,13 +62,20 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.TimeZone;
+import java.util.List;
+import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import net.glxn.qrgen.QRCode;
+import net.glxn.qrgen.image.ImageType;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.wallet.DeterministicSeed;
@@ -90,9 +98,10 @@ public class SeedWordsView extends ActivatableView {
private final SimpleBooleanProperty seedWordsValid = new SimpleBooleanProperty(false);
private ChangeListener seedWordsTextAreaChangeListener;
private final BooleanProperty seedWordsEdited = new SimpleBooleanProperty();
- private String seedWordText;
+ private char[] seedWordText;
private LocalDate walletCreationDate;
+ private ImageView qrCodeImageView;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
@@ -100,10 +109,10 @@ public class SeedWordsView extends ActivatableView {
@Inject
private SeedWordsView(WalletsManager walletsManager,
- OpenOfferManager openOfferManager,
- XmrWalletService xmrWalletService,
- WalletPasswordWindow walletPasswordWindow,
- @Named(Config.STORAGE_DIR) File storageDir) {
+ OpenOfferManager openOfferManager,
+ XmrWalletService xmrWalletService,
+ WalletPasswordWindow walletPasswordWindow,
+ @Named(Config.STORAGE_DIR) File storageDir) {
this.walletsManager = walletsManager;
this.openOfferManager = openOfferManager;
this.xmrWalletService = xmrWalletService;
@@ -120,6 +129,16 @@ public class SeedWordsView extends ActivatableView {
displaySeedWordsTextArea.setMaxHeight(70);
displaySeedWordsTextArea.setEditable(false);
+ // Create a container for seed words and QR code
+ HBox hBox = new HBox();
+ qrCodeImageView = new ImageView();
+ qrCodeImageView.setFitWidth(150);
+ qrCodeImageView.setFitHeight(150);
+ hBox.getChildren().add(displaySeedWordsTextArea); // Seed words text
+ hBox.getChildren().add(qrCodeImageView); // QR code image view
+
+ root.add(hBox, 0, ++gridRow, 2, 1); // Add the HBox to the grid
+
datePicker = addTopLabelDatePicker(root, ++gridRow, Res.get("seed.date"), 10).second;
datePicker.setMouseTransparent(true);
@@ -141,7 +160,7 @@ public class SeedWordsView extends ActivatableView {
addTitledGroupBg(root, ++gridRow, 1, Res.get("shared.information"), Layout.GROUP_DISTANCE);
addMultilineLabel(root, gridRow, Res.get("account.seed.info"),
- Layout.FIRST_ROW_AND_GROUP_DISTANCE);
+ Layout.FIRST_ROW_AND_GROUP_DISTANCE);
seedWordsValidChangeListener = (observable, oldValue, newValue) -> {
if (newValue) {
@@ -206,20 +225,20 @@ public class SeedWordsView extends ActivatableView {
.onAction(() -> {
DontShowAgainLookup.dontShowAgain(key, true);
initSeedWords(xmrWalletService.getWallet().getSeed());
- showSeedScreen();
+ Platform.runLater(this::showSeedScreen);
})
.closeButtonText(Res.get("shared.no"))
.show();
} else {
initSeedWords(xmrWalletService.getWallet().getSeed());
- showSeedScreen();
+ Platform.runLater(this::showSeedScreen);
}
}
}
@Override
protected void deactivate() {
- displaySeedWordsTextArea.setText("");
+ clearSensitiveData();
datePicker.setValue(null);
// seedWordsValid.removeListener(seedWordsValidChangeListener);
@@ -235,21 +254,43 @@ public class SeedWordsView extends ActivatableView {
// restoreDatePicker.getStyleClass().remove("validation-error");
}
+ private void clearSensitiveData() {
+ if (seedWordText != null) {
+ // Overwrite seed words in memory before clearing
+ java.util.Arrays.fill(seedWordText, ' ');
+ }
+ }
+
+ private void generateAndDisplayQRCode(String seedWords) {
+ Platform.runLater(() -> {
+ // Using cakewallet's QR Code format
+ String formattedSeed = "monero-wallet:?seed=" + seedWords.replace(" ", "+");
+
+ // Generate QR Code using the net.glxn.qrgen library
+ ByteArrayInputStream qrCodeStream = new ByteArrayInputStream(
+ QRCode.from(formattedSeed).to(ImageType.PNG).stream().toByteArray()
+ );
+ Image qrCodeImage = new Image(qrCodeStream);
+ qrCodeImageView.setImage(qrCodeImage);
+ });
+ }
+
private void askForPassword() {
walletPasswordWindow.headLine(Res.get("account.seed.enterPw")).onSuccess(() -> {
initSeedWords(xmrWalletService.getWallet().getSeed());
- showSeedScreen();
+ Platform.runLater(this::showSeedScreen);
}).hideForgotPasswordButton().show();
}
private void initSeedWords(String seed) {
- seedWordText = seed;
+ seedWordText = seed.toCharArray();
}
private void showSeedScreen() {
- displaySeedWordsTextArea.setText(seedWordText);
+ displaySeedWordsTextArea.setText(new String(seedWordText));
walletCreationDate = Instant.ofEpochSecond(xmrWalletService.getWalletCreationDate()).atZone(ZoneId.systemDefault()).toLocalDate();
datePicker.setValue(walletCreationDate);
+ generateAndDisplayQRCode(new String(seedWordText));
}
private void onRestore() {
@@ -316,7 +357,7 @@ public class SeedWordsView extends ActivatableView {
LocalDateTime localDateTime = walletDate.atStartOfDay().minusDays(1);
long date = localDateTime.toEpochSecond(ZoneOffset.UTC);
- DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
+ DeterministicSeed seed = new DeterministicSeed(List.of(new String(seedWordText).split(" ")), null, "", date);
SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir);
}
}