mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-28 01:15:26 -04:00
support password prompt in legacy ui
Co-authored-by: niyid <neeyeed@gmail.com>
This commit is contained in:
parent
99653fe40d
commit
4dde53f0e8
19 changed files with 357 additions and 272 deletions
|
@ -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
|
||||
|
|
|
@ -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<Boolean> 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<Boolean> loginAccount() {
|
||||
CompletableFuture<Boolean> 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;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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<Consumer<KeyParameter>> requestWalletPasswordHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<Alert> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
||||
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<String> txKeyImages = new HashSet<String>();
|
||||
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
||||
if (!txKeyImages.equals(new HashSet<String>(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 -------------------------------
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue