From c7048fd152ad79cba4a08cacc29ada30a13f5230 Mon Sep 17 00:00:00 2001 From: XMRZombie <monerozombie@proton.me> Date: Thu, 12 Dec 2024 18:52:32 +0000 Subject: [PATCH 1/4] Update SeedWordsView.java Uses net.glxn.qrgen.QRCode to show seedwords as a QRCode for external wallet backup --- .../content/seedwords/SeedWordsView.java | 257 ++++++------------ 1 file changed, 79 insertions(+), 178 deletions(-) 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..0ada28e55d 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 <http://www.gnu.org/licenses/>. - */ +* 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 <http://www.gnu.org/licenses/>. +*/ /* - * 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 <http://www.gnu.org/licenses/>. - */ +* 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 <http://www.gnu.org/licenses/>. +*/ package haveno.desktop.main.account.content.seedwords; @@ -53,25 +53,29 @@ 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.File; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.util.TimeZone; 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; -//import static javafx.beans.binding.Bindings.createBooleanBinding; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; @FxmlView public class SeedWordsView extends ActivatableView<GridPane, Void> { @@ -93,6 +97,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { private String seedWordText; private LocalDate walletCreationDate; + private ImageView qrCodeImageView; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, lifecycle @@ -100,10 +105,10 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { @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,28 +125,21 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { 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); - // TODO: to re-enable restore functionality: - // - uncomment code throughout this file - // - support getting wallet's restore height - // - support translating between date and restore height - // - clear XmrAddressEntries which are incompatible with new wallet and other tests - // - update mnemonic validation and restore calls - - // addTitledGroupBg(root, ++gridRow, 3, Res.get("seed.restore.title"), Layout.GROUP_DISTANCE); - // seedWordsTextArea = addTopLabelTextArea(root, gridRow, Res.get("seed.seedWords"), "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - // seedWordsTextArea.getStyleClass().add("wallet-seed-words"); - // seedWordsTextArea.setPrefHeight(40); - // seedWordsTextArea.setMaxHeight(40); - - // restoreDatePicker = addTopLabelDatePicker(root, ++gridRow, Res.get("seed.date"), 10).second; - // restoreButton = addPrimaryActionButtonAFterGroup(root, ++gridRow, Res.get("seed.restore")); - 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); + addMultilineLabel(root, gridRow, Res.get("account.seed.info"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); seedWordsValidChangeListener = (observable, oldValue, newValue) -> { if (newValue) { @@ -165,22 +163,6 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { @Override public void activate() { - // seedWordsValid.addListener(seedWordsValidChangeListener); - // seedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener); - // restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !seedWordsEdited.get(), - // seedWordsValid, seedWordsEdited)); - - // restoreButton.setOnAction(e -> { - // new Popup().information(Res.get("account.seed.restore.info")) - // .closeButtonText(Res.get("shared.cancel")) - // .actionButtonText(Res.get("account.seed.restore.ok")) - // .onAction(this::onRestore) - // .show(); - // }); - - // seedWordsTextArea.getStyleClass().remove("validation-error"); - // restoreDatePicker.getStyleClass().remove("validation-error"); - String key = "showBackupWarningAtSeedPhrase"; if (DontShowAgainLookup.showAgain(key)) { new Popup().warning(Res.get("account.seed.backup.warning")) @@ -199,40 +181,27 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { if (xmrWalletService.isWalletEncrypted()) { askForPassword(); } else { - String key = "showSeedWordsWarning"; - if (DontShowAgainLookup.showAgain(key)) { - new Popup().warning(Res.get("account.seed.warn.noPw.msg")) - .actionButtonText(Res.get("account.seed.warn.noPw.yes")) - .onAction(() -> { - DontShowAgainLookup.dontShowAgain(key, true); - initSeedWords(xmrWalletService.getWallet().getSeed()); - showSeedScreen(); - }) - .closeButtonText(Res.get("shared.no")) - .show(); - } else { - initSeedWords(xmrWalletService.getWallet().getSeed()); - showSeedScreen(); - } + initSeedWords(xmrWalletService.getWallet().getSeed()); + showSeedScreen(); } } - @Override - protected void deactivate() { - displaySeedWordsTextArea.setText(""); - datePicker.setValue(null); + private void showSeedScreen() { + displaySeedWordsTextArea.setText(seedWordText); + walletCreationDate = Instant.ofEpochSecond(xmrWalletService.getWalletCreationDate()).atZone(ZoneId.systemDefault()).toLocalDate(); + datePicker.setValue(walletCreationDate); - // seedWordsValid.removeListener(seedWordsValidChangeListener); - // seedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener); - // restoreButton.disableProperty().unbind(); - // restoreButton.setOnAction(null); + // Generate QR code from the seed words and display it + generateAndDisplayQRCode(seedWordText); + } - // seedWordsTextArea.setText(""); - - // restoreDatePicker.setValue(null); - - // seedWordsTextArea.getStyleClass().remove("validation-error"); - // restoreDatePicker.getStyleClass().remove("validation-error"); + private void generateAndDisplayQRCode(String seedWords) { + // Generate QR Code using the net.glxn.qrgen library + ByteArrayInputStream qrCodeStream = new ByteArrayInputStream( + QRCode.from(seedWords).to(ImageType.PNG).stream().toByteArray() + ); + Image qrCodeImage = new Image(qrCodeStream); + qrCodeImageView.setImage(qrCodeImage); } private void askForPassword() { @@ -246,77 +215,9 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { seedWordText = seed; } - private void showSeedScreen() { - displaySeedWordsTextArea.setText(seedWordText); - walletCreationDate = Instant.ofEpochSecond(xmrWalletService.getWalletCreationDate()).atZone(ZoneId.systemDefault()).toLocalDate(); - datePicker.setValue(walletCreationDate); - } - - private void onRestore() { - if (walletsManager.hasPositiveBalance()) { - new Popup().warning(Res.get("seed.warn.walletNotEmpty.msg")) - .actionButtonText(Res.get("seed.warn.walletNotEmpty.restore")) - .onAction(this::checkIfEncrypted) - .closeButtonText(Res.get("seed.warn.walletNotEmpty.emptyWallet")) - .show(); - } else { - checkIfEncrypted(); - } - } - - private void checkIfEncrypted() { - if (walletsManager.areWalletsEncrypted()) { - new Popup().information(Res.get("seed.warn.notEncryptedAnymore")) - .closeButtonText(Res.get("shared.no")) - .actionButtonText(Res.get("shared.yes")) - .onAction(this::doRestoreDateCheck) - .show(); - } else { - doRestoreDateCheck(); - } - } - - private void doRestoreDateCheck() { - if (restoreDatePicker.getValue() == null) { - // Provide feedback when attempting to restore a wallet from seed words without specifying a date - new Popup().information(Res.get("seed.warn.walletDateEmpty")) - .closeButtonText(Res.get("shared.no")) - .actionButtonText(Res.get("shared.yes")) - .onAction(this::doRestore) - .show(); - } else { - doRestore(); - } - } - - private LocalDate getWalletDate() { - LocalDate walletDate = restoreDatePicker.getValue(); - // Even though no current Haveno wallet could have been created before the v0.5 release date (2017.06.28), - // the user may want to import from a seed generated by another wallet. - // So use when the BIP39 standard was finalised (2013.10.09) as the oldest possible wallet date. - LocalDate oldestWalletDate = LocalDate.ofInstant( - Instant.ofEpochMilli(MnemonicCode.BIP39_STANDARDISATION_TIME_SECS * 1000), - TimeZone.getDefault().toZoneId()); - if (walletDate == null) { - // No date was specified, perhaps the user doesn't know the wallet date - walletDate = oldestWalletDate; - } else if (walletDate.isBefore(oldestWalletDate)) { - walletDate = oldestWalletDate; - } else if (walletDate.isAfter(LocalDate.now())) { - walletDate = LocalDate.now(); - } - return walletDate; - } - - private void doRestore() { - LocalDate walletDate = getWalletDate(); - // We subtract 1 day to be sure to not have any issues with timezones. Even if we can be sure that the timezone - // is handled correctly it could be that the user created the wallet in one timezone and make a restore at - // a different timezone which could lead in the worst case that he miss the first day of the wallet transactions. - LocalDateTime localDateTime = walletDate.atStartOfDay().minusDays(1); - long date = localDateTime.toEpochSecond(ZoneOffset.UTC); - - DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date); - SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir); + @Override + protected void deactivate() { + displaySeedWordsTextArea.setText(""); + datePicker.setValue(null); } } From 1933cc8b28d5a9304340aa20ca27a7a58b32937d Mon Sep 17 00:00:00 2001 From: XMRZombie <monerozombie@proton.me> Date: Sun, 15 Dec 2024 12:09:49 +0000 Subject: [PATCH 2/4] Update SeedWordsView.java Summaries: - Restored lost code parts. - Added UI updates - Using char for memory safe seed usage --- .../content/seedwords/SeedWordsView.java | 198 +++++++++++++++--- 1 file changed, 168 insertions(+), 30 deletions(-) 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 0ada28e55d..c6303b00fd 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 @@ -53,6 +53,18 @@ 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.nio.CharBuffer; +import java.time.Instant; +import java.time.LocalDate; +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; @@ -68,14 +80,7 @@ import net.glxn.qrgen.image.ImageType; import org.bitcoinj.crypto.MnemonicCode; import org.bitcoinj.crypto.MnemonicException; import org.bitcoinj.wallet.DeterministicSeed; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.List; +//import static javafx.beans.binding.Bindings.createBooleanBinding; @FxmlView public class SeedWordsView extends ActivatableView<GridPane, Void> { @@ -94,7 +99,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { private final SimpleBooleanProperty seedWordsValid = new SimpleBooleanProperty(false); private ChangeListener<String> seedWordsTextAreaChangeListener; private final BooleanProperty seedWordsEdited = new SimpleBooleanProperty(); - private String seedWordText; + private char[] seedWordText; private LocalDate walletCreationDate; private ImageView qrCodeImageView; @@ -138,8 +143,25 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { datePicker = addTopLabelDatePicker(root, ++gridRow, Res.get("seed.date"), 10).second; datePicker.setMouseTransparent(true); + // TODO: to re-enable restore functionality: + // - uncomment code throughout this file + // - support getting wallet's restore height + // - support translating between date and restore height + // - clear XmrAddressEntries which are incompatible with new wallet and other tests + // - update mnemonic validation and restore calls + + // addTitledGroupBg(root, ++gridRow, 3, Res.get("seed.restore.title"), Layout.GROUP_DISTANCE); + // seedWordsTextArea = addTopLabelTextArea(root, gridRow, Res.get("seed.seedWords"), "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + // seedWordsTextArea.getStyleClass().add("wallet-seed-words"); + // seedWordsTextArea.setPrefHeight(40); + // seedWordsTextArea.setMaxHeight(40); + + // restoreDatePicker = addTopLabelDatePicker(root, ++gridRow, Res.get("seed.date"), 10).second; + // restoreButton = addPrimaryActionButtonAFterGroup(root, ++gridRow, Res.get("seed.restore")); + 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); + addMultilineLabel(root, gridRow, Res.get("account.seed.info"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE); seedWordsValidChangeListener = (observable, oldValue, newValue) -> { if (newValue) { @@ -163,6 +185,22 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { @Override public void activate() { + // seedWordsValid.addListener(seedWordsValidChangeListener); + // seedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener); + // restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !seedWordsEdited.get(), + // seedWordsValid, seedWordsEdited)); + + // restoreButton.setOnAction(e -> { + // new Popup().information(Res.get("account.seed.restore.info")) + // .closeButtonText(Res.get("shared.cancel")) + // .actionButtonText(Res.get("account.seed.restore.ok")) + // .onAction(this::onRestore) + // .show(); + // }); + + // seedWordsTextArea.getStyleClass().remove("validation-error"); + // restoreDatePicker.getStyleClass().remove("validation-error"); + String key = "showBackupWarningAtSeedPhrase"; if (DontShowAgainLookup.showAgain(key)) { new Popup().warning(Res.get("account.seed.backup.warning")) @@ -181,43 +219,143 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { if (xmrWalletService.isWalletEncrypted()) { askForPassword(); } else { - initSeedWords(xmrWalletService.getWallet().getSeed()); - showSeedScreen(); + String key = "showSeedWordsWarning"; + if (DontShowAgainLookup.showAgain(key)) { + new Popup().warning(Res.get("account.seed.warn.noPw.msg")) + .actionButtonText(Res.get("account.seed.warn.noPw.yes")) + .onAction(() -> { + DontShowAgainLookup.dontShowAgain(key, true); + initSeedWords(xmrWalletService.getWallet().getSeed()); + Platform.runLater(this::showSeedScreen); + }) + .closeButtonText(Res.get("shared.no")) + .show(); + } else { + initSeedWords(xmrWalletService.getWallet().getSeed()); + Platform.runLater(this::showSeedScreen); + } } } - private void showSeedScreen() { - displaySeedWordsTextArea.setText(seedWordText); - walletCreationDate = Instant.ofEpochSecond(xmrWalletService.getWalletCreationDate()).atZone(ZoneId.systemDefault()).toLocalDate(); - datePicker.setValue(walletCreationDate); + @Override + protected void deactivate() { + clearSensitiveData(); + datePicker.setValue(null); - // Generate QR code from the seed words and display it - generateAndDisplayQRCode(seedWordText); + // seedWordsValid.removeListener(seedWordsValidChangeListener); + // seedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener); + // restoreButton.disableProperty().unbind(); + // restoreButton.setOnAction(null); + + // seedWordsTextArea.setText(""); + + // restoreDatePicker.setValue(null); + + // seedWordsTextArea.getStyleClass().remove("validation-error"); + // 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) { - // Generate QR Code using the net.glxn.qrgen library - ByteArrayInputStream qrCodeStream = new ByteArrayInputStream( - QRCode.from(seedWords).to(ImageType.PNG).stream().toByteArray() - ); - Image qrCodeImage = new Image(qrCodeStream); - qrCodeImageView.setImage(qrCodeImage); + Platform.runLater(() -> { + // Generate QR Code using the net.glxn.qrgen library + ByteArrayInputStream qrCodeStream = new ByteArrayInputStream( + QRCode.from(seedWords).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(); } - @Override - protected void deactivate() { - displaySeedWordsTextArea.setText(""); - datePicker.setValue(null); + private void showSeedScreen() { + displaySeedWordsTextArea.setText(new String(seedWordText)); + walletCreationDate = Instant.ofEpochSecond(xmrWalletService.getWalletCreationDate()).atZone(ZoneId.systemDefault()).toLocalDate(); + datePicker.setValue(walletCreationDate); + generateAndDisplayQRCode(new String(seedWordText)); + } + + private void onRestore() { + if (walletsManager.hasPositiveBalance()) { + new Popup().warning(Res.get("seed.warn.walletNotEmpty.msg")) + .actionButtonText(Res.get("seed.warn.walletNotEmpty.restore")) + .onAction(this::checkIfEncrypted) + .closeButtonText(Res.get("seed.warn.walletNotEmpty.emptyWallet")) + .show(); + } else { + checkIfEncrypted(); + } + } + + private void checkIfEncrypted() { + if (walletsManager.areWalletsEncrypted()) { + new Popup().information(Res.get("seed.warn.notEncryptedAnymore")) + .closeButtonText(Res.get("shared.no")) + .actionButtonText(Res.get("shared.yes")) + .onAction(this::doRestoreDateCheck) + .show(); + } else { + doRestoreDateCheck(); + } + } + + private void doRestoreDateCheck() { + if (restoreDatePicker.getValue() == null) { + // Provide feedback when attempting to restore a wallet from seed words without specifying a date + new Popup().information(Res.get("seed.warn.walletDateEmpty")) + .closeButtonText(Res.get("shared.no")) + .actionButtonText(Res.get("shared.yes")) + .onAction(this::doRestore) + .show(); + } else { + doRestore(); + } + } + + private LocalDate getWalletDate() { + LocalDate walletDate = restoreDatePicker.getValue(); + // Even though no current Haveno wallet could have been created before the v0.5 release date (2017.06.28), + // the user may want to import from a seed generated by another wallet. + // So use when the BIP39 standard was finalised (2013.10.09) as the oldest possible wallet date. + LocalDate oldestWalletDate = LocalDate.ofInstant( + Instant.ofEpochMilli(MnemonicCode.BIP39_STANDARDISATION_TIME_SECS * 1000), + TimeZone.getDefault().toZoneId()); + if (walletDate == null) { + // No date was specified, perhaps the user doesn't know the wallet date + walletDate = oldestWalletDate; + } else if (walletDate.isBefore(oldestWalletDate)) { + walletDate = oldestWalletDate; + } else if (walletDate.isAfter(LocalDate.now())) { + walletDate = LocalDate.now(); + } + return walletDate; + } + + private void doRestore() { + LocalDate walletDate = getWalletDate(); + // We subtract 1 day to be sure to not have any issues with timezones. Even if we can be sure that the timezone + // is handled correctly it could be that the user created the wallet in one timezone and make a restore at + // a different timezone which could lead in the worst case that he miss the first day of the wallet transactions. + LocalDateTime localDateTime = walletDate.atStartOfDay().minusDays(1); + long date = localDateTime.toEpochSecond(ZoneOffset.UTC); + + DeterministicSeed seed = new DeterministicSeed(List.of(new String(seedWordText).split(" ")), null, "", date); + SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir); } } From 5b754bff9d24ea0090899cd5dfd9921ad471c833 Mon Sep 17 00:00:00 2001 From: XMRZombie <monerozombie@proton.me> Date: Sun, 15 Dec 2024 12:17:47 +0000 Subject: [PATCH 3/4] Update SeedWordsView.java Removing unused import --- .../desktop/main/account/content/seedwords/SeedWordsView.java | 1 - 1 file changed, 1 deletion(-) 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 c6303b00fd..b7b9299d6a 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 @@ -56,7 +56,6 @@ import haveno.desktop.util.Layout; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.nio.CharBuffer; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; From 197997ba7f2957b134d9d8da680d887fb46942e0 Mon Sep 17 00:00:00 2001 From: XMRZombie <monerozombie@proton.me> Date: Sun, 15 Dec 2024 12:44:43 +0000 Subject: [PATCH 4/4] Update SeedWordsView.java Using cakewallet's QR Code format ```String formattedSeed = "monero-wallet:?seed=" + seedWords.replace(" ", "+");``` --- .../main/account/content/seedwords/SeedWordsView.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 b7b9299d6a..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 @@ -263,9 +263,12 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> { 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(seedWords).to(ImageType.PNG).stream().toByteArray() + QRCode.from(formattedSeed).to(ImageType.PNG).stream().toByteArray() ); Image qrCodeImage = new Image(qrCodeStream); qrCodeImageView.setImage(qrCodeImage);