Implement business logic
MODIFY
WalletWasabi.Gui/Tabs/WalletManager/GenerateWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/GenerateWalletViewModel.cs
@@ -86,26 +86,26 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
public string Password
{
- get { return _password; }
- set { this.RaiseAndSetIfChanged(ref _password, value); }
+ get => _password;
+ set => this.RaiseAndSetIfChanged(ref _password, value);
}
public string WalletName
{
- get { return _walletName; }
- set { this.RaiseAndSetIfChanged(ref _walletName, value); }
+ get => _walletName;
+ set => this.RaiseAndSetIfChanged(ref _walletName, value);
}
public bool TermsAccepted
{
- get { return _termsAccepted; }
- set { this.RaiseAndSetIfChanged(ref _termsAccepted, value); }
+ get => _termsAccepted;
+ set => this.RaiseAndSetIfChanged(ref _termsAccepted, value);
}
public string ValidationMessage
{
- get { return _validationMessage; }
- set { this.RaiseAndSetIfChanged(ref _validationMessage, value); }
+ get => _validationMessage;
+ set => this.RaiseAndSetIfChanged(ref _validationMessage, value);
}
public ReactiveCommand GenerateCommand { get; }

MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletView.xaml
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletView.xaml
</controls:GroupBox>
<StackPanel DockPanel.Dock="Bottom" Margin="20 10">
<DockPanel LastChildFill="True">
- <Button IsVisible="{Binding RequirePassword}" Content="Test Password" Command="{Binding GenerateCommand}" DockPanel.Dock="Right" Margin="10 0 0 0" />
+ <Button IsVisible="{Binding RequirePassword}" Content="Test Password" Command="{Binding TestPasswordCommand}" DockPanel.Dock="Right" Margin="10 0 0 0" />
<Button Command="{Binding LoadCommand}" DockPanel.Dock="Right">
<StackPanel Orientation="Horizontal">
<controls:Spinner Height="20" Width="20" IsVisible="{Binding IsBusy}" Margin="0 0 8 0" />
</DockPanel>
<TextBlock Text="{Binding WarningMessage}" Classes="warningMessage" />
<TextBlock Text="{Binding ValidationMessage}" Classes="errorMessage" />
+ <TextBlock Text="{Binding SuccessMessage}" Classes="validationMessage" Foreground="Green" />
</StackPanel>
</StackPanel>
<controls:GroupBox Title="{Binding Title}" TextBlock.FontSize="30" Padding="20" BorderThickness="0">

MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
@@ -15,17 +15,21 @@ using WalletWasabi.Gui.Controls.WalletExplorer;
using Avalonia.Controls;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using WalletWasabi.Helpers;
namespace WalletWasabi.Gui.Tabs.WalletManager
{
internal class LoadWalletViewModel : CategoryViewModel
{
private ObservableCollection<string> _wallets;
+ private string _password;
private string _selectedWallet;
+ private bool _isWalletSelected;
private bool _isWalletOpened;
private bool _canLoadWallet;
private string _warningMessage;
private string _validationMessage;
+ private string _successMessage;
private bool _isBusy;
private string _loadButtonText;
@@ -35,21 +39,31 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
public LoadWalletViewModel(WalletManagerViewModel owner, bool requirePassword) : base(requirePassword ? "Test Password" : "Load Wallet")
{
Owner = owner;
+ Password = "";
RequirePassword = requirePassword;
- _wallets = new ObservableCollection<string>();
+ Wallets = new ObservableCollection<string>();
this.WhenAnyValue(x => x.SelectedWallet)
- .Subscribe(selectedWallet => CanLoadWallet = !string.IsNullOrEmpty(selectedWallet) && !IsWalletOpened);
+ .Subscribe(selectedWallet => SetWalletStates());
this.WhenAnyValue(x => x.IsWalletOpened)
- .Subscribe(isWalletOpened => CanLoadWallet = !string.IsNullOrEmpty(SelectedWallet) && !isWalletOpened);
+ .Subscribe(isWalletOpened => SetWalletStates());
- this.WhenAnyValue(x => x.IsWalletOpened)
- .Subscribe(isWalletOpened => WarningMessage = isWalletOpened
- ? "There is already an open wallet. Restart the application to open another one."
- : string.Empty);
+ this.WhenAnyValue(x => x.Password).Subscribe(x =>
+ {
+ if (x.NotNullAndNotEmpty())
+ {
+ char lastChar = x.Last();
+ if (lastChar == '\r' || lastChar == '\n') // If the last character is cr or lf then act like it'd be a sign to do the job.
+ {
+ Password = x.TrimEnd('\r', '\n');
+ LoadKeyManager(requirePassword: true);
+ }
+ }
+ });
LoadCommand = ReactiveCommand.Create(LoadWalletAsync, this.WhenAnyValue(x => x.CanLoadWallet));
+ TestPasswordCommand = ReactiveCommand.Create(() => LoadKeyManager(requirePassword: true), this.WhenAnyValue(x => x.IsWalletSelected));
OpenFolderCommand = ReactiveCommand.Create(OpenWalletsFolder);
SetLoadButtonText(IsBusy);
}
@@ -60,12 +74,24 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
set => this.RaiseAndSetIfChanged(ref _wallets, value);
}
+ public string Password
+ {
+ get => _password;
+ set => this.RaiseAndSetIfChanged(ref _password, value);
+ }
+
public string SelectedWallet
{
get => _selectedWallet;
set => this.RaiseAndSetIfChanged(ref _selectedWallet, value);
}
+ public bool IsWalletSelected
+ {
+ get => _isWalletSelected;
+ set => this.RaiseAndSetIfChanged(ref _isWalletSelected, value);
+ }
+
public bool IsWalletOpened
{
get => _isWalletOpened;
@@ -84,6 +110,33 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
set => this.RaiseAndSetIfChanged(ref _validationMessage, value);
}
+ public string SuccessMessage
+ {
+ get => _successMessage;
+ set => this.RaiseAndSetIfChanged(ref _successMessage, value);
+ }
+
+ private void SetWarningMessage(string message)
+ {
+ WarningMessage = message;
+ ValidationMessage = "";
+ SuccessMessage = "";
+ }
+
+ private void SetValidationMessage(string message)
+ {
+ WarningMessage = "";
+ ValidationMessage = message;
+ SuccessMessage = "";
+ }
+
+ private void SetSuccessMessage(string message)
+ {
+ WarningMessage = "";
+ ValidationMessage = "";
+ SuccessMessage = message;
+ }
+
public void SetLoadButtonText(bool isBusy)
{
LoadButtonText = isBusy ? "Loading..." : "Load Wallet";
@@ -97,11 +150,7 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
public bool CanLoadWallet
{
- get
- {
- if (IsBusy) return false;
- return _canLoadWallet;
- }
+ get => _canLoadWallet;
set => this.RaiseAndSetIfChanged(ref _canLoadWallet, value);
}
@@ -110,15 +159,18 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
get => _isBusy;
set
{
- CanLoadWallet = !value;
- SetLoadButtonText(value);
this.RaiseAndSetIfChanged(ref _isBusy, value);
+
+ SetLoadButtonText(value);
+ SetWalletStates();
}
}
public override void OnCategorySelected()
{
- _wallets.Clear();
+ Wallets.Clear();
+ Password = "";
+ SetValidationMessage("");
if (!File.Exists(Global.WalletsDir))
{
@@ -129,25 +181,44 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
var walletFiles = directoryInfo.GetFiles("*.json", SearchOption.TopDirectoryOnly).OrderByDescending(t => t.LastAccessTimeUtc);
foreach (var file in walletFiles)
{
- _wallets.Add(Path.GetFileNameWithoutExtension(file.FullName));
+ Wallets.Add(Path.GetFileNameWithoutExtension(file.FullName));
}
- if (_wallets.Any())
- {
- SelectedWallet = _wallets.First();
+ SelectedWallet = Wallets.FirstOrDefault();
+ SetWalletStates();
}
- IsWalletOpened = !(Global.WalletService is null);
- ValidationMessage = null;
+ private void SetWalletStates()
+ {
+ IsWalletSelected = !string.IsNullOrEmpty(SelectedWallet);
+
+ IsWalletOpened = Global.WalletService != null;
+ // If not busy loading.
+ // And wallet is selected.
+ // And no wallet is opened.
+ CanLoadWallet = !IsBusy && IsWalletSelected && !IsWalletOpened;
+
+ if (IsWalletOpened)
+ {
+ SetWarningMessage("There is already an open wallet. Restart the application to open another one.");
+ }
}
public ReactiveCommand LoadCommand { get; }
+ public ReactiveCommand TestPasswordCommand { get; }
- public async Task LoadWalletAsync()
+ public KeyManager LoadKeyManager(bool requirePassword)
{
try
{
- IsBusy = true;
+ var password = Guard.Correct(Password); // Don't let whitespaces to the beginning and to the end.
+ Password = ""; // Clear password field.
+
+ if (string.IsNullOrEmpty(SelectedWallet))
+ {
+ SetValidationMessage("No wallet selected.");
+ return null;
+ }
var walletFullPath = Path.Combine(Global.WalletsDir, SelectedWallet + ".json");
var walletBackupFullPath = Path.Combine(Global.WalletBackupsDir, SelectedWallet + ".json");
@@ -155,19 +226,14 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
{
// The selected wallet is not available any more (someone deleted it?).
OnCategorySelected();
- SelectedWallet = null;
- ValidationMessage = "The selected wallet and its backup doesn't exsist, did you delete them?";
- return;
+ SetValidationMessage("The selected wallet and its backup doesn't exsist, did you delete them?");
+ return null;
}
- try
- {
- await Task.Run(async () =>
- {
KeyManager keyManager = null;
try
{
- keyManager = LoadWallet(walletFullPath);
+ keyManager = LoadKeyManager(walletFullPath);
}
catch (Exception ex)
{
@@ -194,9 +260,51 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
}
File.Copy(walletBackupFullPath, walletFullPath);
- keyManager = LoadWallet(walletFullPath);
+ keyManager = LoadKeyManager(walletFullPath);
+ }
+
+ // Only check requirepassword here, because the above checks are applicable to loadwallet, too and we are using this function from load wallet.
+ if (requirePassword)
+ {
+ if (!keyManager.TestPassword(password))
+ {
+ SetValidationMessage("Wrong password.");
+ return null;
+ }
+ else
+ {
+ SetSuccessMessage("Correct password.");
+ }
+ }
+
+ return keyManager;
+ }
+ catch (Exception ex)
+ {
+ // Initialization failed.
+ SetValidationMessage(ex.ToTypeMessageString());
+ Logger.LogError<LoadWalletViewModel>(ex);
+
+ return null;
+ }
}
+ public async Task LoadWalletAsync()
+ {
+ try
+ {
+ IsBusy = true;
+
+ var keyManager = LoadKeyManager(RequirePassword);
+ if (keyManager is null)
+ {
+ return;
+ }
+
+ try
+ {
+ await Task.Run(async () =>
+ {
await Global.InitializeWalletServiceAsync(keyManager);
});
// Successffully initialized.
@@ -215,7 +323,7 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
catch (Exception ex)
{
// Initialization failed.
- ValidationMessage = ex.ToTypeMessageString();
+ SetValidationMessage(ex.ToTypeMessageString());
Logger.LogError<LoadWalletViewModel>(ex);
await Global.DisposeInWalletDependentServicesAsync();
}
@@ -223,10 +331,11 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
finally
{
IsBusy = false;
+ SetWalletStates();
}
}
- private KeyManager LoadWallet(string walletFullPath)
+ private KeyManager LoadKeyManager(string walletFullPath)
{
KeyManager keyManager;
var walletFileInfo = new FileInfo(walletFullPath)

MODIFY
WalletWasabi/KeyManagement/KeyManager.cs
WalletWasabi/KeyManagement/KeyManager.cs
@@ -292,15 +292,7 @@ namespace WalletWasabi.KeyManagement
public IEnumerable<(ExtKey secret, HdPubKey pubKey)> GetSecretsAndPubKeyPairs(string password, params Script[] scripts)
{
- Key secret;
- try
- {
- secret = EncryptedSecret.GetKey(password);
- }
- catch (SecurityException ex)
- {
- throw new SecurityException("Invalid password.", ex);
- }
+ Key secret = GetSecret(password);
var extKey = new ExtKey(secret, ChainCode);
var extKeysAndPubs = new List<(ExtKey secret, HdPubKey pubKey)>();
@@ -319,6 +311,34 @@ namespace WalletWasabi.KeyManagement
}
}
+ public Key GetSecret(string password)
+ {
+ Key secret;
+ try
+ {
+ secret = EncryptedSecret.GetKey(password);
+ }
+ catch (SecurityException ex)
+ {
+ throw new SecurityException("Invalid password.", ex);
+ }
+
+ return secret;
+ }
+
+ public bool TestPassword(string password)
+ {
+ try
+ {
+ GetSecret(password);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
/// <summary>
/// Make sure there's always clean keys generated and indexed.
/// </summary>

Implement business logic