diff --git a/core/src/main/java/bisq/core/api/CoreAccountService.java b/core/src/main/java/bisq/core/api/CoreAccountService.java index 2158c96678..6004222686 100644 --- a/core/src/main/java/bisq/core/api/CoreAccountService.java +++ b/core/src/main/java/bisq/core/api/CoreAccountService.java @@ -116,6 +116,7 @@ public class CoreAccountService { public void changePassword(String oldPassword, String newPassword) { if (!isAccountOpen()) throw new IllegalStateException("Cannot change password on unopened account"); + if ("".equals(oldPassword)) oldPassword = null; // normalize to null if (!StringUtils.equals(this.password, oldPassword)) throw new IllegalStateException("Incorrect password"); if (newPassword != null && newPassword.length() < 8) throw new IllegalStateException("Password must be at least 8 characters"); keyStorage.saveKeyRing(keyRing, oldPassword, newPassword); @@ -125,6 +126,12 @@ public class CoreAccountService { } } + public void verifyPassword(String password) throws IncorrectPasswordException { + if (!StringUtils.equals(this.password, password)) { + throw new IncorrectPasswordException("Incorrect password"); + } + } + public void closeAccount() { if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account"); keyRing.lockKeys(); // closed account means the keys are locked diff --git a/core/src/main/java/bisq/core/app/HavenoExecutable.java b/core/src/main/java/bisq/core/app/HavenoExecutable.java index 659e7e221e..f7ad4cea50 100644 --- a/core/src/main/java/bisq/core/app/HavenoExecutable.java +++ b/core/src/main/java/bisq/core/app/HavenoExecutable.java @@ -31,13 +31,14 @@ import bisq.core.trade.HavenoUtils; import bisq.core.trade.TradeManager; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.txproof.xmr.XmrTxProofService; + import bisq.network.p2p.P2PService; import bisq.common.UserThread; import bisq.common.app.AppModule; -import bisq.common.config.HavenoHelpFormatter; import bisq.common.config.Config; import bisq.common.config.ConfigException; +import bisq.common.config.HavenoHelpFormatter; import bisq.common.crypto.IncorrectPasswordException; import bisq.common.handlers.ResultHandler; import bisq.common.persistence.PersistenceManager; @@ -51,8 +52,11 @@ import com.google.inject.Guice; import com.google.inject.Injector; import java.io.Console; + import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -135,7 +139,6 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven // thread the application is running and we don't run into thread interference. protected abstract void launchApplication(); - /////////////////////////////////////////////////////////////////////////////////////////// // If application is a JavaFX application we need wait for onApplicationLaunched /////////////////////////////////////////////////////////////////////////////////////////// @@ -165,12 +168,25 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven }); // Attempt to login, subclasses should implement interactive login and or rpc login. - if (!isReadOnly && loginAccount()) { - readAllPersisted(this::startApplication); - } else { - log.warn("Running application in readonly mode"); - startApplication(); - } + CompletableFuture loginFuture = loginAccount(); + loginFuture.whenComplete((result, throwable) -> { + if (throwable != null) { + log.error("Error logging in to account", throwable); + shutDownNoPersist(null, false); + return; + } + try { + if (!isReadOnly && loginFuture.get()) { + readAllPersisted(this::startApplication); + } else { + log.warn("Running application in readonly mode"); + startApplication(); + } + } catch (InterruptedException | ExecutionException e) { + log.error("An error occurred: {}", e.getMessage()); + e.printStackTrace(); + } + }); } /** @@ -199,19 +215,26 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven * * @return true if account is opened successfully. */ - protected boolean loginAccount() { + protected CompletableFuture loginAccount() { + CompletableFuture result = new CompletableFuture<>(); if (accountService.accountExists()) { log.info("Account already exists, attempting to open"); try { accountService.openAccount(null); + result.complete(accountService.isAccountOpen()); } catch (IncorrectPasswordException ipe) { log.info("Account password protected, password required"); + result.complete(false); } } else if (!config.passwordRequired) { log.info("Creating Haveno account with null password"); accountService.createAccount(null); + result.complete(accountService.isAccountOpen()); + } else { + log.info("Account does not exist and password is required"); + result.complete(false); } - return accountService.isAccountOpen(); + return result; } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/app/HavenoHeadlessApp.java b/core/src/main/java/bisq/core/app/HavenoHeadlessApp.java index 5b22c8760a..4280525ad5 100644 --- a/core/src/main/java/bisq/core/app/HavenoHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/HavenoHeadlessApp.java @@ -83,7 +83,6 @@ public class HavenoHeadlessApp implements HeadlessApp { bisqSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg)); bisqSetup.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg)); bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(() -> log.info("onShowFirstPopupIfResyncSPVRequestedHandler")); - bisqSetup.setRequestWalletPasswordHandler(aesKeyHandler -> log.info("onRequestWalletPasswordHandler")); bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler")); bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert)); bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification)); diff --git a/core/src/main/java/bisq/core/app/HavenoSetup.java b/core/src/main/java/bisq/core/app/HavenoSetup.java index 3bcdcb3d17..58f5074a0a 100644 --- a/core/src/main/java/bisq/core/app/HavenoSetup.java +++ b/core/src/main/java/bisq/core/app/HavenoSetup.java @@ -83,8 +83,6 @@ import javafx.beans.value.ChangeListener; import javafx.collections.SetChangeListener; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; @@ -160,9 +158,6 @@ public class HavenoSetup { private Runnable showFirstPopupIfResyncSPVRequestedHandler; @Setter @Nullable - private Consumer> requestWalletPasswordHandler; - @Setter - @Nullable private Consumer displayAlertHandler; @Setter @Nullable @@ -215,30 +210,30 @@ public class HavenoSetup { @Inject public HavenoSetup(DomainInitialisation domainInitialisation, - P2PNetworkSetup p2PNetworkSetup, - WalletAppSetup walletAppSetup, - WalletsManager walletsManager, - WalletsSetup walletsSetup, - XmrWalletService xmrWalletService, - BtcWalletService btcWalletService, - P2PService p2PService, - PrivateNotificationManager privateNotificationManager, - SignedWitnessStorageService signedWitnessStorageService, - TradeManager tradeManager, - OpenOfferManager openOfferManager, - Preferences preferences, - User user, - AlertManager alertManager, - Config config, - AccountAgeWitnessService accountAgeWitnessService, - TorSetup torSetup, - @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, - LocalBitcoinNode localBitcoinNode, - AppStartupState appStartupState, - Socks5ProxyProvider socks5ProxyProvider, - MediationManager mediationManager, - RefundManager refundManager, - ArbitrationManager arbitrationManager) { + P2PNetworkSetup p2PNetworkSetup, + WalletAppSetup walletAppSetup, + WalletsManager walletsManager, + WalletsSetup walletsSetup, + XmrWalletService xmrWalletService, + BtcWalletService btcWalletService, + P2PService p2PService, + PrivateNotificationManager privateNotificationManager, + SignedWitnessStorageService signedWitnessStorageService, + TradeManager tradeManager, + OpenOfferManager openOfferManager, + Preferences preferences, + User user, + AlertManager alertManager, + Config config, + AccountAgeWitnessService accountAgeWitnessService, + TorSetup torSetup, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + LocalBitcoinNode localBitcoinNode, + AppStartupState appStartupState, + Socks5ProxyProvider socks5ProxyProvider, + MediationManager mediationManager, + RefundManager refundManager, + ArbitrationManager arbitrationManager) { this.domainInitialisation = domainInitialisation; this.p2PNetworkSetup = p2PNetworkSetup; this.walletAppSetup = walletAppSetup; @@ -264,6 +259,8 @@ public class HavenoSetup { this.refundManager = refundManager; this.arbitrationManager = arbitrationManager; + xmrWalletService.setHavenoSetup(this); + MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode); } @@ -411,9 +408,10 @@ public class HavenoSetup { return; } log.warn("startupTimeout called"); - if (walletsManager.areWalletsEncrypted()) - walletInitialized.addListener(walletInitializedListener); - else if (displayTorNetworkSettingsHandler != null) + //TODO (niyid) This has a part to play in the display of the password prompt +// if (walletsManager.areWalletsEncrypted()) +// walletInitialized.addListener(walletInitializedListener); + if (displayTorNetworkSettingsHandler != null) displayTorNetworkSettingsHandler.accept(true); log.info("Set log level for org.berndpruenster.netlayer classes to DEBUG to show more details for " + @@ -453,35 +451,11 @@ public class HavenoSetup { private void initWallet() { log.info("Init wallet"); havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet); - Runnable walletPasswordHandler = () -> { - log.info("Wallet password required"); - havenoSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword); - if (p2pNetworkReady.get()) - p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true); - - if (requestWalletPasswordHandler != null) { - requestWalletPasswordHandler.accept(aesKey -> { - walletsManager.setAesKey(aesKey); - walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(), - aesKey); - if (getResyncSpvSemaphore()) { - if (showFirstPopupIfResyncSPVRequestedHandler != null) - showFirstPopupIfResyncSPVRequestedHandler.run(); - } else { - // TODO no guarantee here that the wallet is really fully initialized - // We would need a new walletInitializedButNotEncrypted state to track - // Usually init is fast and we have our wallet initialized at that state though. - walletInitialized.set(true); - } - }); - } - }; walletAppSetup.init(chainFileLockedExceptionHandler, spvFileCorruptedHandler, getResyncSpvSemaphore(), showFirstPopupIfResyncSPVRequestedHandler, showPopupIfInvalidBtcConfigHandler, - walletPasswordHandler, () -> { if (allBasicServicesInitialized) { checkForLockedUpFunds(); @@ -867,5 +841,7 @@ public class HavenoSetup { return p2PNetworkSetup.getP2pNetworkLabelId(); } - + public BooleanProperty getWalletInitialized() { + return walletInitialized; + } } diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java index 3f0c13c787..c934817ac3 100644 --- a/core/src/main/java/bisq/core/app/WalletAppSetup.java +++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java @@ -108,7 +108,6 @@ public class WalletAppSetup { boolean isSpvResyncRequested, @Nullable Runnable showFirstPopupIfResyncSPVRequestedHandler, @Nullable Runnable showPopupIfInvalidBtcConfigHandler, - Runnable walletPasswordHandler, Runnable downloadCompleteHandler, Runnable walletInitializedHandler) { log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}", @@ -171,17 +170,7 @@ public class WalletAppSetup { walletsSetup.initialize(null, () -> { - // We only check one wallet as we apply encryption to all or none - if (walletsManager.areWalletsEncrypted() && !coreContext.isApiUser()) { - walletPasswordHandler.run(); - } else { - if (isSpvResyncRequested && !coreContext.isApiUser()) { - if (showFirstPopupIfResyncSPVRequestedHandler != null) - showFirstPopupIfResyncSPVRequestedHandler.run(); - } else { - walletInitializedHandler.run(); - } - } + walletInitializedHandler.run(); }, exception -> { if (exception instanceof InvalidHostException && showPopupIfInvalidBtcConfigHandler != null) { diff --git a/core/src/main/java/bisq/core/btc/model/EncryptedConnectionList.java b/core/src/main/java/bisq/core/btc/model/EncryptedConnectionList.java index 15f323498d..5e4a979ea0 100644 --- a/core/src/main/java/bisq/core/btc/model/EncryptedConnectionList.java +++ b/core/src/main/java/bisq/core/btc/model/EncryptedConnectionList.java @@ -307,7 +307,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa try { return Encryption.decrypt(encrypted, secret); } catch (CryptoException e) { - throw new IllegalArgumentException("Illegal old password", e); + throw new IllegalArgumentException("Incorrect password", e); } } diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletsManager.java b/core/src/main/java/bisq/core/btc/wallet/WalletsManager.java index 6bb8e44591..6a6ec321e5 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletsManager.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletsManager.java @@ -23,8 +23,6 @@ import bisq.common.crypto.ScryptUtil; import bisq.common.handlers.ExceptionHandler; import bisq.common.handlers.ResultHandler; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import org.bitcoinj.crypto.KeyCrypter; import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.wallet.DeterministicSeed; diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java index 7c81379b00..b5a4b5e832 100644 --- a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java @@ -10,6 +10,7 @@ import bisq.common.util.Utilities; import bisq.core.api.AccountServiceListener; import bisq.core.api.CoreAccountService; import bisq.core.api.CoreMoneroConnectionsService; +import bisq.core.app.HavenoSetup; import bisq.core.btc.listeners.XmrBalanceListener; import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.model.XmrAddressEntryList; @@ -92,6 +93,7 @@ public class XmrWalletService { private final CoreMoneroConnectionsService connectionsService; private final XmrAddressEntryList xmrAddressEntryList; private final WalletsSetup walletsSetup; + private final File walletDir; private final File xmrWalletFile; private final int rpcBindPort; @@ -103,6 +105,8 @@ public class XmrWalletService { private final Map> txCache = new HashMap>(); private boolean isShutDown = false; + private HavenoSetup havenoSetup; + @Inject XmrWalletService(CoreAccountService accountService, CoreMoneroConnectionsService connectionsService, @@ -148,8 +152,8 @@ public class XmrWalletService { @Override public void onPasswordChanged(String oldPassword, String newPassword) { log.info(getClass() + "accountservice.onPasswordChanged()"); - if (oldPassword == null) oldPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD; - if (newPassword == null) newPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD; + if (oldPassword == null || oldPassword.isEmpty()) oldPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD; + if (newPassword == null || newPassword.isEmpty()) newPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD; changeWalletPasswords(oldPassword, newPassword); } }); @@ -383,41 +387,41 @@ public class XmrWalletService { // verify tx not submitted to pool MoneroTx tx = daemon.getTx(txHash); if (tx != null) throw new RuntimeException("Tx is already submitted"); - + // submit tx to pool MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency? if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result)); tx = getTx(txHash); - + // verify key images if (keyImages != null) { Set txKeyImages = new HashSet(); for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex()); if (!txKeyImages.equals(new HashSet(keyImages))) throw new Error("Tx inputs do not match claimed key images"); } - + // verify unlock height if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0"); - + // verify trade fee String feeAddress = HavenoUtils.getTradeFeeAddress(); MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress); if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee"); if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount()); - + // verify miner fee BigInteger feeEstimate = getFeeEstimate(tx.getWeight()); double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal? if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee()); log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff); - + // verify sufficient security deposit check = wallet.checkTxKey(txHash, txKey, address); if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount"); BigInteger minSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger(); BigInteger actualSecurityDeposit = check.getReceivedAmount().subtract(sendAmount); if (actualSecurityDeposit.compareTo(minSecurityDeposit) < 0) throw new RuntimeException("Security deposit amount is not enough, needed " + minSecurityDeposit + " but was " + actualSecurityDeposit); - + // verify deposit amount + miner fee within dust tolerance BigInteger minDepositAndFee = sendAmount.add(securityDeposit).subtract(new BigDecimal(tx.getFee()).multiply(new BigDecimal(1.0 - DUST_TOLERANCE)).toBigInteger()); BigInteger actualDepositAndFee = check.getReceivedAmount().add(tx.getFee()); @@ -558,6 +562,9 @@ public class XmrWalletService { System.out.println("Monero wallet balance: " + wallet.getBalance(0)); System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0)); + // notify setup that main wallet is initialized + havenoSetup.getWalletInitialized().set(true); // TODO: change to listener pattern? + // register internal listener to notify external listeners wallet.addListener(new XmrWalletListener()); } @@ -961,6 +968,10 @@ public class XmrWalletService { log.info("\n" + tracePrefix + ":" + sb.toString()); } + public void setHavenoSetup(HavenoSetup havenoSetup) { + this.havenoSetup = havenoSetup; + } + // -------------------------------- HELPERS ------------------------------- /** diff --git a/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java b/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java index e58ed5aa9e..e26bccb9f7 100644 --- a/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java +++ b/daemon/src/main/java/bisq/daemon/app/HavenoDaemonMain.java @@ -17,11 +17,10 @@ package bisq.daemon.app; -import bisq.core.app.ConsoleInput; -import bisq.core.app.HavenoHeadlessAppMain; -import bisq.core.app.HavenoSetup; import bisq.core.api.AccountServiceListener; +import bisq.core.app.ConsoleInput; import bisq.core.app.CoreModule; +import bisq.core.app.HavenoHeadlessAppMain; import bisq.common.UserThread; import bisq.common.app.AppModule; @@ -32,7 +31,10 @@ import bisq.common.persistence.PersistenceManager; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.Console; + import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; @@ -43,7 +45,7 @@ import lombok.extern.slf4j.Slf4j; import bisq.daemon.grpc.GrpcServer; @Slf4j -public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSetup.HavenoSetupListener { +public class HavenoDaemonMain extends HavenoHeadlessAppMain { private GrpcServer grpcServer; @@ -139,53 +141,60 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet * Start the grpcServer to allow logging in remotely. */ @Override - protected boolean loginAccount() { - boolean opened = super.loginAccount(); + protected CompletableFuture loginAccount() { + CompletableFuture opened = super.loginAccount(); // Start rpc server in case login is coming in from rpc grpcServer = injector.getInstance(GrpcServer.class); - if (!opened) { - // Nonblocking, we need to stop if the login occurred through rpc. - // TODO: add a mode to mask password - ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); - Thread t = new Thread(() -> { - interactiveLogin(reader); - }); + CompletableFuture inputResult = new CompletableFuture(); + try { + if (opened.get()) { + grpcServer.start(); + return opened; + } else { - // Handle asynchronous account opens. - // Will need to also close and reopen account. - AccountServiceListener accountListener = new AccountServiceListener() { - @Override public void onAccountCreated() { onLogin(); } - @Override public void onAccountOpened() { onLogin(); } - private void onLogin() { - log.info("Logged in successfully"); - reader.cancel(); // closing the reader will stop all read attempts and end the interactive login thread + // Nonblocking, we need to stop if the login occurred through rpc. + // TODO: add a mode to mask password + ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); + Thread t = new Thread(() -> { + interactiveLogin(reader); + }); + + // Handle asynchronous account opens. + // Will need to also close and reopen account. + AccountServiceListener accountListener = new AccountServiceListener() { + @Override public void onAccountCreated() { onLogin(); } + @Override public void onAccountOpened() { onLogin(); } + private void onLogin() { + log.info("Logged in successfully"); + reader.cancel(); // closing the reader will stop all read attempts and end the interactive login thread + } + }; + accountService.addListener(accountListener); + + // start server after the listener is registered + grpcServer.start(); + + try { + // Wait until interactive login or rpc. Check one more time if account is open to close race condition. + if (!accountService.isAccountOpen()) { + log.info("Interactive login required"); + t.start(); + t.join(); + } + } catch (InterruptedException e) { + // expected } - }; - accountService.addListener(accountListener); - // start server after the listener is registered - grpcServer.start(); - - try { - // Wait until interactive login or rpc. Check one more time if account is open to close race condition. - if (!accountService.isAccountOpen()) { - log.info("Interactive login required"); - t.start(); - t.join(); - } - } catch (InterruptedException e) { - // expected + accountService.removeListener(accountListener); + inputResult.complete(accountService.isAccountOpen()); } - - accountService.removeListener(accountListener); - opened = accountService.isAccountOpen(); - } else { - grpcServer.start(); + } catch (InterruptedException | ExecutionException e) { + inputResult.completeExceptionally(e); } - return opened; + return inputResult; } /** diff --git a/desktop/src/main/java/bisq/desktop/app/HavenoApp.java b/desktop/src/main/java/bisq/desktop/app/HavenoApp.java index ffcbbf46b8..b9d32ce727 100644 --- a/desktop/src/main/java/bisq/desktop/app/HavenoApp.java +++ b/desktop/src/main/java/bisq/desktop/app/HavenoApp.java @@ -67,8 +67,8 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; -import javafx.geometry.Rectangle2D; import javafx.geometry.BoundingBox; +import javafx.geometry.Rectangle2D; import java.util.ArrayList; import java.util.List; @@ -128,6 +128,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler { } public void startApplication(Runnable onApplicationStartedHandler) { + log.info("Running startApplication..."); try { mainView = loadMainView(injector); mainView.setOnApplicationStartedHandler(onApplicationStartedHandler); @@ -149,7 +150,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler { .show(); new Thread(() -> { gracefulShutDownHandler.gracefulShutDown(() -> { - log.debug("App shutdown complete"); + log.info("App shutdown complete"); }); }).start(); shutDownRequested = true; diff --git a/desktop/src/main/java/bisq/desktop/app/HavenoAppMain.java b/desktop/src/main/java/bisq/desktop/app/HavenoAppMain.java index 0258513d9d..1bc6738835 100644 --- a/desktop/src/main/java/bisq/desktop/app/HavenoAppMain.java +++ b/desktop/src/main/java/bisq/desktop/app/HavenoAppMain.java @@ -20,6 +20,7 @@ package bisq.desktop.app; import bisq.desktop.common.UITimer; import bisq.desktop.common.view.guice.InjectorViewFactory; import bisq.desktop.setup.DesktopPersistedDataHost; +import bisq.desktop.util.ImageUtil; import bisq.core.app.AvoidStandbyModeService; import bisq.core.app.HavenoExecutable; @@ -27,10 +28,25 @@ import bisq.core.app.HavenoExecutable; import bisq.common.UserThread; import bisq.common.app.AppModule; import bisq.common.app.Version; - +import bisq.common.crypto.IncorrectPasswordException; import javafx.application.Application; import javafx.application.Platform; +import javafx.stage.Stage; + +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + import lombok.extern.slf4j.Slf4j; @Slf4j @@ -139,4 +155,86 @@ public class HavenoAppMain extends HavenoExecutable { // Therefore, calling this as part of onApplicationStarted() log.info("Using JavaFX {}", System.getProperty("javafx.version")); } + + @Override + protected CompletableFuture loginAccount() { + + // attempt default login + CompletableFuture result = super.loginAccount(); + try { + if (result.get()) return result; + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + + // login using dialog + CompletableFuture dialogResult = new CompletableFuture<>(); + Platform.setImplicitExit(false); + Platform.runLater(() -> { + + // show password dialog until account open + String errorMessage = null; + while (!accountService.isAccountOpen()) { + + // create the password dialog + PasswordDialog passwordDialog = new PasswordDialog(errorMessage); + + // wait for user to enter password + Optional passwordResult = passwordDialog.showAndWait(); + if (passwordResult.isPresent()) { + try { + accountService.openAccount(passwordResult.get()); + dialogResult.complete(accountService.isAccountOpen()); + } catch (IncorrectPasswordException e) { + errorMessage = "Incorrect password"; + } + } else { + // if the user cancelled the dialog, complete the passwordFuture exceptionally + dialogResult.completeExceptionally(new Exception("Password dialog cancelled")); + break; + } + } + }); + return dialogResult; + } + + private class PasswordDialog extends Dialog { + + public PasswordDialog(String errorMessage) { + setTitle("Enter Password"); + setHeaderText("Please enter your Haveno password:"); + + // Add an icon to the dialog + Stage stage = (Stage) getDialogPane().getScene().getWindow(); + stage.getIcons().add(ImageUtil.getImageByPath("lock.png")); + + // Create the password field + PasswordField passwordField = new PasswordField(); + passwordField.setPromptText("Password"); + + // Create the error message field + Label errorMessageField = new Label(errorMessage); + errorMessageField.setTextFill(Color.color(1, 0, 0)); + + // Set the dialog content + VBox vbox = new VBox(10); + vbox.getChildren().addAll(new ImageView(ImageUtil.getImageByPath("logo_splash.png")), passwordField, errorMessageField); + getDialogPane().setContent(vbox); + + // Add OK and Cancel buttons + ButtonType okButton = new ButtonType("OK", ButtonBar.ButtonData.OK_DONE); + ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(okButton, cancelButton); + + // Convert the result to a string when the OK button is clicked + setResultConverter(buttonType -> { + if (buttonType == okButton) { + return passwordField.getText(); + } else { + new Thread(() -> HavenoApp.getShutDownHandler().run()).start(); + return null; + } + }); + } + } } diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index f61ef28a78..28771a2f38 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -18,7 +18,6 @@ package bisq.desktop.main; import bisq.desktop.Navigation; -import bisq.desktop.app.HavenoApp; import bisq.desktop.common.model.ViewModel; import bisq.desktop.components.TxIdTextField; import bisq.desktop.main.account.AccountView; @@ -109,7 +108,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener { - private final HavenoSetup bisqSetup; + private final HavenoSetup havenoSetup; private final CoreMoneroConnectionsService connectionService; private final User user; private final BalancePresentation balancePresentation; @@ -154,7 +153,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public MainViewModel(HavenoSetup bisqSetup, + public MainViewModel(HavenoSetup havenoSetup, CoreMoneroConnectionsService connectionService, XmrWalletService xmrWalletService, User user, @@ -179,7 +178,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener TorNetworkSettingsWindow torNetworkSettingsWindow, CorruptedStorageFileHandler corruptedStorageFileHandler, Navigation navigation) { - this.bisqSetup = bisqSetup; + this.havenoSetup = havenoSetup; this.connectionService = connectionService; this.user = user; this.balancePresentation = balancePresentation; @@ -211,7 +210,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener GUIUtil.setPreferences(preferences); setupHandlers(); - bisqSetup.addHavenoSetupListener(this); + havenoSetup.addHavenoSetupListener(this); } @@ -303,7 +302,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener } void onOpenDownloadWindow() { - bisqSetup.displayAlertIfPresent(user.getDisplayedAlert(), true); + havenoSetup.displayAlertIfPresent(user.getDisplayedAlert(), true); } void setPriceFeedComboBoxItem(PriceFeedComboBoxItem item) { @@ -316,34 +315,30 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener /////////////////////////////////////////////////////////////////////////////////////////// private void setupHandlers() { - bisqSetup.setDisplayTacHandler(acceptedHandler -> UserThread.runAfter(() -> { + havenoSetup.setDisplayTacHandler(acceptedHandler -> UserThread.runAfter(() -> { //noinspection FunctionalExpressionCanBeFolded tacWindow.onAction(acceptedHandler::run).show(); }, 1)); - bisqSetup.setDisplayTorNetworkSettingsHandler(show -> { + havenoSetup.setDisplayTorNetworkSettingsHandler(show -> { if (show) { torNetworkSettingsWindow.show(); } else if (torNetworkSettingsWindow.isDisplayed()) { torNetworkSettingsWindow.hide(); } }); - bisqSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg) + havenoSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg) .actionButtonText(Res.get("settings.net.reSyncSPVChainButton")) .onAction(() -> GUIUtil.reSyncSPVChain(preferences)) .show()); - bisqSetup.setChainFileLockedExceptionHandler(msg -> new Popup().warning(msg) + havenoSetup.setChainFileLockedExceptionHandler(msg -> new Popup().warning(msg) .useShutDownButton() .show()); - bisqSetup.setLockedUpFundsHandler(msg -> new Popup().width(850).warning(msg).show()); - bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(this::showFirstPopupIfResyncSPVRequested); - bisqSetup.setRequestWalletPasswordHandler(aesKeyHandler -> walletPasswordWindow - .onAesKey(aesKeyHandler::accept) - .onClose(() -> HavenoApp.getShutDownHandler().run()) - .show()); + havenoSetup.setLockedUpFundsHandler(msg -> new Popup().width(850).warning(msg).show()); + havenoSetup.setShowFirstPopupIfResyncSPVRequestedHandler(this::showFirstPopupIfResyncSPVRequested); - bisqSetup.setDisplayUpdateHandler((alert, key) -> new DisplayUpdateDownloadWindow(alert, config) + havenoSetup.setDisplayUpdateHandler((alert, key) -> new DisplayUpdateDownloadWindow(alert, config) .actionButtonText(Res.get("displayUpdateDownloadWindow.button.downloadLater")) .onAction(() -> { preferences.dontShowAgain(key, false); // update later @@ -353,19 +348,19 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener preferences.dontShowAgain(key, true); // ignore update }) .show()); - bisqSetup.setDisplayAlertHandler(alert -> new DisplayAlertMessageWindow() + havenoSetup.setDisplayAlertHandler(alert -> new DisplayAlertMessageWindow() .alertMessage(alert) .closeButtonText(Res.get("shared.close")) .onClose(() -> user.setDisplayedAlert(alert)) .show()); - bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> + havenoSetup.setDisplayPrivateNotificationHandler(privateNotification -> new Popup().headLine(Res.get("popup.privateNotification.headline")) .attention(privateNotification.getMessage()) .onClose(privateNotificationManager::removePrivateNotification) .useIUnderstandButton() .show()); - bisqSetup.setDisplaySecurityRecommendationHandler(key -> {}); - bisqSetup.setDisplayLocalhostHandler(key -> { + havenoSetup.setDisplaySecurityRecommendationHandler(key -> {}); + havenoSetup.setDisplayLocalhostHandler(key -> { if (!DevEnv.isDevMode()) { Popup popup = new Popup().backgroundInfo(Res.get("popup.bitcoinLocalhostNode.msg")) .dontShowAgainId(key); @@ -373,31 +368,31 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener popupQueue.add(popup); } }); - bisqSetup.setDisplaySignedByArbitratorHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( + havenoSetup.setDisplaySignedByArbitratorHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( key, "popup.accountSigning.signedByArbitrator")); - bisqSetup.setDisplaySignedByPeerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( + havenoSetup.setDisplaySignedByPeerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( key, "popup.accountSigning.signedByPeer", String.valueOf(SignedWitnessService.SIGNER_AGE_DAYS))); - bisqSetup.setDisplayPeerLimitLiftedHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( + havenoSetup.setDisplayPeerLimitLiftedHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( key, "popup.accountSigning.peerLimitLifted")); - bisqSetup.setDisplayPeerSignerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( + havenoSetup.setDisplayPeerSignerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( key, "popup.accountSigning.peerSigner")); - bisqSetup.setWrongOSArchitectureHandler(msg -> new Popup().warning(msg).show()); + havenoSetup.setWrongOSArchitectureHandler(msg -> new Popup().warning(msg).show()); - bisqSetup.setRejectedTxErrorMessageHandler(msg -> new Popup().width(850).warning(msg).show()); + havenoSetup.setRejectedTxErrorMessageHandler(msg -> new Popup().width(850).warning(msg).show()); - bisqSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig); + havenoSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig); - bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> { + havenoSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> { // We copy the array as we will mutate it later showRevolutAccountUpdateWindow(new ArrayList<>(revolutAccountList)); }); - bisqSetup.setAmazonGiftCardAccountsUpdateHandler(amazonGiftCardAccountList -> { + havenoSetup.setAmazonGiftCardAccountsUpdateHandler(amazonGiftCardAccountList -> { // We copy the array as we will mutate it later showAmazonGiftCardAccountUpdateWindow(new ArrayList<>(amazonGiftCardAccountList)); }); - bisqSetup.setOsxKeyLoggerWarningHandler(() -> { }); - bisqSetup.setQubesOSInfoHandler(() -> { + havenoSetup.setOsxKeyLoggerWarningHandler(() -> { }); + havenoSetup.setQubesOSInfoHandler(() -> { String key = "qubesOSSetupInfo"; if (preferences.showAgain(key)) { new Popup().information(Res.get("popup.info.qubesOSSetupInfo")) @@ -407,14 +402,14 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener } }); - bisqSetup.setDownGradePreventionHandler(lastVersion -> { + havenoSetup.setDownGradePreventionHandler(lastVersion -> { new Popup().warning(Res.get("popup.warn.downGradePrevention", lastVersion, Version.VERSION)) .useShutDownButton() .hideCloseButton() .show(); }); - bisqSetup.setTorAddressUpgradeHandler(() -> new Popup().information(Res.get("popup.info.torMigration.msg")) + havenoSetup.setTorAddressUpgradeHandler(() -> new Popup().information(Res.get("popup.info.torMigration.msg")) .actionButtonTextWithGoTo("navigation.account.backup") .onAction(() -> { navigation.setReturnPath(navigation.getCurrentPath()); @@ -430,9 +425,9 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener .warning(Res.get("popup.error.takeOfferRequestFailed", errorMessage)) .show()); - bisqSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress()); + havenoSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress()); - bisqSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show()); + havenoSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show()); this.footerVersionInfo.setValue("v" + Version.VERSION); this.getNewVersionAvailableProperty().addListener((observable, oldValue, newValue) -> { @@ -538,10 +533,10 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener private void showFirstPopupIfResyncSPVRequested() { Popup firstPopup = new Popup(); firstPopup.information(Res.get("settings.net.reSyncSPVAfterRestart")).show(); - if (bisqSetup.getBtcSyncProgress().get() == 1) { + if (havenoSetup.getBtcSyncProgress().get() == 1) { showSecondPopupIfResyncSPVRequested(firstPopup); } else { - bisqSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> { + havenoSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> { if ((double) newValue == 1) showSecondPopupIfResyncSPVRequested(firstPopup); }); @@ -596,7 +591,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener } private void updateBtcSyncProgress() { - final DoubleProperty btcSyncProgress = bisqSetup.getBtcSyncProgress(); + final DoubleProperty btcSyncProgress = havenoSetup.getBtcSyncProgress(); combinedSyncProgress.set(btcSyncProgress.doubleValue()); } @@ -635,7 +630,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener /////////////////////////////////////////////////////////////////////////////////////////// BooleanProperty getNewVersionAvailableProperty() { - return bisqSetup.getNewVersionAvailableProperty(); + return havenoSetup.getNewVersionAvailableProperty(); } StringProperty getNumOpenSupportTickets() { @@ -670,7 +665,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener // Wallet StringProperty getBtcInfo() { final StringProperty combinedInfo = new SimpleStringProperty(); - combinedInfo.bind(bisqSetup.getBtcInfo()); + combinedInfo.bind(havenoSetup.getBtcInfo()); return combinedInfo; } @@ -685,44 +680,44 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener } StringProperty getWalletServiceErrorMsg() { - return bisqSetup.getWalletServiceErrorMsg(); + return havenoSetup.getWalletServiceErrorMsg(); } StringProperty getBtcSplashSyncIconId() { - return bisqSetup.getBtcSplashSyncIconId(); + return havenoSetup.getBtcSplashSyncIconId(); } BooleanProperty getUseTorForBTC() { - return bisqSetup.getUseTorForBTC(); + return havenoSetup.getUseTorForBTC(); } // P2P StringProperty getP2PNetworkInfo() { - return bisqSetup.getP2PNetworkInfo(); + return havenoSetup.getP2PNetworkInfo(); } BooleanProperty getSplashP2PNetworkAnimationVisible() { - return bisqSetup.getSplashP2PNetworkAnimationVisible(); + return havenoSetup.getSplashP2PNetworkAnimationVisible(); } StringProperty getP2pNetworkWarnMsg() { - return bisqSetup.getP2pNetworkWarnMsg(); + return havenoSetup.getP2pNetworkWarnMsg(); } StringProperty getP2PNetworkIconId() { - return bisqSetup.getP2PNetworkIconId(); + return havenoSetup.getP2PNetworkIconId(); } StringProperty getP2PNetworkStatusIconId() { - return bisqSetup.getP2PNetworkStatusIconId(); + return havenoSetup.getP2PNetworkStatusIconId(); } BooleanProperty getUpdatedDataReceived() { - return bisqSetup.getUpdatedDataReceived(); + return havenoSetup.getUpdatedDataReceived(); } StringProperty getP2pNetworkLabelId() { - return bisqSetup.getP2pNetworkLabelId(); + return havenoSetup.getP2pNetworkLabelId(); } // marketPricePresentation diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/password/PasswordView.java b/desktop/src/main/java/bisq/desktop/main/account/content/password/PasswordView.java index 937a8b3801..9ea856830a 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/password/PasswordView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/password/PasswordView.java @@ -27,16 +27,15 @@ import bisq.desktop.components.TitledGroupBg; import bisq.desktop.main.MainView; import bisq.desktop.main.account.AccountView; import bisq.desktop.main.account.content.backup.BackupView; -import bisq.desktop.main.account.content.seedwords.SeedWordsView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.Layout; import bisq.desktop.util.validation.PasswordValidator; + +import bisq.core.api.CoreAccountService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.locale.Res; -import bisq.common.crypto.ScryptUtil; -import bisq.common.util.Tuple4; -import org.bitcoinj.crypto.KeyCrypterScrypt; +import bisq.common.util.Tuple4; import javax.inject.Inject; @@ -61,6 +60,7 @@ public class PasswordView extends ActivatableView { private final WalletsManager walletsManager; private final PasswordValidator passwordValidator; private final Navigation navigation; + private final CoreAccountService accountService; private PasswordTextField passwordField; private PasswordTextField repeatedPasswordField; @@ -77,10 +77,11 @@ public class PasswordView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private PasswordView(WalletsManager walletsManager, PasswordValidator passwordValidator, Navigation navigation) { + private PasswordView(CoreAccountService accountService, WalletsManager walletsManager, PasswordValidator passwordValidator, Navigation navigation) { this.walletsManager = walletsManager; this.passwordValidator = passwordValidator; this.navigation = navigation; + this.accountService = accountService; } @Override @@ -141,41 +142,38 @@ public class PasswordView extends ActivatableView { deriveStatusLabel.setText(Res.get("password.deriveKey")); busyAnimation.play(); - KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt(); - ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> { - deriveStatusLabel.setText(""); - busyAnimation.stop(); - - if (walletsManager.areWalletsEncrypted()) { - if (walletsManager.checkAESKey(aesKey)) { - walletsManager.decryptWallets(aesKey); - new Popup() - .feedback(Res.get("password.walletDecrypted")) - .show(); - backupWalletAndResetFields(); - } else { - pwButton.setDisable(false); - new Popup() - .warning(Res.get("password.wrongPw")) - .show(); - } - } else { - try { - walletsManager.encryptWallets(keyCrypterScrypt, aesKey); - new Popup() - .feedback(Res.get("password.walletEncrypted")) - .show(); - backupWalletAndResetFields(); - walletsManager.clearBackup(); - } catch (Throwable t) { - new Popup() - .warning(Res.get("password.walletEncryptionFailed")) - .show(); - } + if (walletsManager.areWalletsEncrypted()) { + try { + accountService.changePassword(password, null); + new Popup() + .feedback(Res.get("password.walletDecrypted")) + .show(); + backupWalletAndResetFields(); + } catch (Throwable t) { + pwButton.setDisable(false); + new Popup() + .warning(Res.get("password.wrongPw")) + .show(); } - setText(); - updatePasswordListeners(); - }); + } else { + try { + accountService.changePassword(accountService.getPassword(), password); + new Popup() + .feedback(Res.get("password.walletEncrypted")) + .show(); + backupWalletAndResetFields(); + walletsManager.clearBackup(); + } catch (Throwable t) { + new Popup() + .warning(Res.get("password.walletEncryptionFailed")) + .show(); + } + } + setText(); + updatePasswordListeners(); + + deriveStatusLabel.setText(""); + busyAnimation.stop(); } private void backupWalletAndResetFields() { diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java index 569c7fe737..4224c37e7d 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java @@ -218,7 +218,7 @@ public class SeedWordsView extends ActivatableView { } private void askForPassword() { - walletPasswordWindow.headLine(Res.get("account.seed.enterPw")).onAesKey(aesKey -> { + walletPasswordWindow.headLine(Res.get("account.seed.enterPw")).onSuccess(() -> { initSeedWords(xmrWalletService.getWallet().getMnemonic()); showSeedScreen(); }).hideForgotPasswordButton().show(); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java index 9b55d67b49..05b9364746 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/BtcEmptyWalletWindow.java @@ -121,14 +121,7 @@ public final class BtcEmptyWalletWindow extends Overlay { emptyWalletButton.setDisable(!isBalanceSufficient && addressInputTextField.getText().length() > 0); emptyWalletButton.setOnAction(e -> { if (addressInputTextField.getText().length() > 0 && isBalanceSufficient) { - if (btcWalletService.isEncrypted()) { - walletPasswordWindow - .onAesKey(this::doEmptyWallet) - .onClose(this::blurAgain) - .show(); - } else { - doEmptyWallet(null); - } + log.warn(getClass().getSimpleName() + ".addContent() needs updated for XMR"); } }); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 81128f68e3..c96b1d88e7 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -29,7 +29,6 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.support.dispute.agent.DisputeAgentLookupMap; import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.trade.Contract; import bisq.core.trade.Trade; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java index e1d6bdba0e..8d33d1e920 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java @@ -25,18 +25,15 @@ import bisq.desktop.main.SharedPresentation; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.Layout; -import bisq.desktop.util.Transitions; - +import bisq.core.api.CoreAccountService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.locale.Res; import bisq.core.offer.OpenOfferManager; -import bisq.common.UserThread; import bisq.common.config.Config; -import bisq.common.crypto.ScryptUtil; +import bisq.common.crypto.IncorrectPasswordException; import bisq.common.util.Tuple2; -import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.MnemonicCode; import org.bitcoinj.crypto.MnemonicException; import org.bitcoinj.wallet.DeterministicSeed; @@ -65,8 +62,6 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; -import org.bouncycastle.crypto.params.KeyParameter; - import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; @@ -75,8 +70,6 @@ import java.time.ZoneOffset; import java.io.File; import java.io.IOException; -import java.util.concurrent.TimeUnit; - import lombok.extern.slf4j.Slf4j; import static bisq.desktop.util.FormBuilder.addPasswordTextField; @@ -88,12 +81,13 @@ import static javafx.beans.binding.Bindings.createBooleanBinding; @Slf4j public class WalletPasswordWindow extends Overlay { + private final CoreAccountService accountService; private final WalletsManager walletsManager; private final OpenOfferManager openOfferManager; private File storageDir; private Button unlockButton; - private AesKeyHandler aesKeyHandler; + private WalletPasswordHandler passwordHandler; private PasswordTextField passwordTextField; private Button forgotPasswordButton; private Button restoreButton; @@ -111,14 +105,16 @@ public class WalletPasswordWindow extends Overlay { // Interface /////////////////////////////////////////////////////////////////////////////////////////// - public interface AesKeyHandler { - void onAesKey(KeyParameter aesKey); + public interface WalletPasswordHandler { + void onSuccess(); } @Inject - private WalletPasswordWindow(WalletsManager walletsManager, + private WalletPasswordWindow(CoreAccountService accountService, + WalletsManager walletsManager, OpenOfferManager openOfferManager, @Named(Config.STORAGE_DIR) File storageDir) { + this.accountService = accountService; this.walletsManager = walletsManager; this.openOfferManager = openOfferManager; this.storageDir = storageDir; @@ -149,8 +145,8 @@ public class WalletPasswordWindow extends Overlay { display(); } - public WalletPasswordWindow onAesKey(AesKeyHandler aesKeyHandler) { - this.aesKeyHandler = aesKeyHandler; + public WalletPasswordWindow onSuccess(WalletPasswordHandler passwordHandler) { + this.passwordHandler = passwordHandler; return this; } @@ -213,27 +209,16 @@ public class WalletPasswordWindow extends Overlay { unlockButton.setOnAction(e -> { String password = passwordTextField.getText(); checkArgument(password.length() < 500, Res.get("password.tooLong")); - KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt(); - if (keyCrypterScrypt != null) { - busyAnimation.play(); - deriveStatusLabel.setText(Res.get("password.deriveKey")); - ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> { - if (walletsManager.checkAESKey(aesKey)) { - if (aesKeyHandler != null) - aesKeyHandler.onAesKey(aesKey); - - hide(); - } else { - busyAnimation.stop(); - deriveStatusLabel.setText(""); - - UserThread.runAfter(() -> new Popup() - .warning(Res.get("password.wrongPw")) - .onClose(this::blurAgain).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); - } - }); - } else { - log.error("wallet.getKeyCrypter() is null, that must not happen."); + try { + accountService.verifyPassword(password); + if (passwordHandler != null) passwordHandler.onSuccess(); + hide(); + } catch (IncorrectPasswordException e2) { + busyAnimation.stop(); + deriveStatusLabel.setText(""); + new Popup() + .warning(Res.get("password.wrongPw")) + .onClose(this::blurAgain).show(); } }); diff --git a/desktop/src/main/java/bisq/desktop/util/ImageUtil.java b/desktop/src/main/java/bisq/desktop/util/ImageUtil.java index a844d01d9f..1b87c30af2 100644 --- a/desktop/src/main/java/bisq/desktop/util/ImageUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/ImageUtil.java @@ -70,6 +70,10 @@ public class ImageUtil { } } + public static Image getImageByPath(String imagePath) { + return getImageByUrl("/images/" + imagePath); + } + // determine if this is a MacOS retina display // https://stackoverflow.com/questions/20767708/how-do-you-detect-a-retina-display-in-java#20767802 public static boolean isRetina() { diff --git a/desktop/src/main/resources/images/lock.png b/desktop/src/main/resources/images/lock.png new file mode 100644 index 0000000000..3a4bba5d91 Binary files /dev/null and b/desktop/src/main/resources/images/lock.png differ