Code refactor - pw separation
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
@@ -235,12 +235,12 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
BuildTransactionCommand = ReactiveCommand.CreateFromTask(async () =>
{
+ bool isCompatibilityPasswordUsed = false;
try
{
IsBusy = true;
MainWindowViewModel.Instance.StatusBar.TryAddStatus(StatusBarStatus.BuildingTransaction);
- Password = Guard.Correct(Password);
Label = Label.Trim(',', ' ').Trim();
if (!IsMax && string.IsNullOrWhiteSpace(Label))
{
@@ -316,6 +316,21 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
MainWindowViewModel.Instance.StatusBar.TryRemoveStatus(StatusBarStatus.DequeuingSelectedCoins);
}
+ try
+ {
+ PasswordHelper.GetMasterExtKey(KeyManager, Password, out string compatiblityPasswordUsed); // We could use TryPassword but we need the exception.
+ if (compatiblityPasswordUsed != null)
+ {
+ isCompatibilityPasswordUsed = true;
+ Password = compatiblityPasswordUsed; // Overwrite the password for BuildTransaction function.
+ }
+ }
+ catch (Exception ex)
+ {
+ SetWarningMessage(ex.ToTypeMessageString());
+ return;
+ }
+
BuildTransactionResult result = await Task.Run(() => Global.WalletService.BuildTransaction(Password, new[] { operation }, feeTarget: null, feeRate: feeRate, allowUnconfirmed: true, allowedInputs: selectedCoinReferences));
if (IsTransactionBuilder)
@@ -406,6 +421,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
MainWindowViewModel.Instance.StatusBar.TryRemoveStatus(StatusBarStatus.BuildingTransaction, StatusBarStatus.SigningTransaction, StatusBarStatus.BroadcastingTransaction);
IsBusy = false;
+
+ if (isCompatibilityPasswordUsed)
+ {
+ WarningMessage = PasswordHelper.CompatibilityPasswordWarnMessage;
+ }
}
},
this.WhenAny(x => x.IsMax, x => x.AmountText, x => x.Address, x => x.IsBusy,
@@ -812,11 +832,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
ResetUi();
SetSuccessMessage(successMessage);
-
- if (KeyManager.IsCompatibilityPasswordUsed)
- {
- WarningMessage = Constants.CompatibilityPasswordWarnMessage;
- }
}
catch (Exception ex)
{

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletInfoViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletInfoViewModel.cs
@@ -58,13 +58,12 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
}
else
{
- Password = Guard.Correct(Password);
- var secret = KeyManager.GetMasterExtKey(Password);
+ var secret = PasswordHelper.GetMasterExtKey(KeyManager, Password, out string isCompatibilityPasswordUsed);
Password = "";
- if (KeyManager.IsCompatibilityPasswordUsed)
+ if (isCompatibilityPasswordUsed != null)
{
- SetWarningMessage(Constants.CompatibilityPasswordWarnMessage);
+ SetWarningMessage(PasswordHelper.CompatibilityPasswordWarnMessage);
}
string master = secret.GetWif(Global.Network).ToWif();

MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
@@ -599,12 +599,12 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
// 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))
+ if (PasswordHelper.TryPassword(keyManager, password, out string compatibilityPasswordUsed))
{
SuccessMessage = "Correct password.";
- if (keyManager.IsCompatibilityPasswordUsed)
+ if (compatibilityPasswordUsed != null)
{
- WarningMessage = Constants.CompatibilityPasswordWarnMessage;
+ WarningMessage = PasswordHelper.CompatibilityPasswordWarnMessage;
ValidationMessage = "";
}

MODIFY
WalletWasabi/Helpers/Constants.cs
WalletWasabi/Helpers/Constants.cs
@@ -13,8 +13,6 @@ namespace WalletWasabi.Helpers
public const uint ProtocolVersion_WITNESS_VERSION = 70012;
- public const int MaxPasswordLength = 150;
-
public static readonly NodeRequirement NodeRequirements = new NodeRequirement
{
RequiredServices = NodeServices.NODE_WITNESS,
@@ -95,7 +93,5 @@ namespace WalletWasabi.Helpers
public const int DefaultRegTestBitcoinCoreRpcPort = 18443;
public const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
- public const string CompatibilityPasswordWarnMessage = "Compatibility password was used! Please consider generating a new wallet to ensure recoverability!";
}
}

MODIFY
WalletWasabi/Helpers/PasswordHelper.cs
WalletWasabi/Helpers/PasswordHelper.cs
@@ -3,23 +3,27 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+using WalletWasabi.KeyManagement;
+using NBitcoin;
+using System.Security;
+using WalletWasabi.Logging;
namespace WalletWasabi.Helpers
{
public static class PasswordHelper
{
+ public const int MaxPasswordLength = 150;
+ public const string CompatibilityPasswordWarnMessage = "Compatibility password was used! Please consider generating a new wallet to ensure recoverability!";
+ public static string PasswordTooLongMessage = $"Password too long (Max {MaxPasswordLength} characters).";
+
public static string[] GetPossiblePasswords(string originalPassword)
{
List<string> possiblePasswords = new List<string>()
{
originalPassword,
+ StringCutIssue(originalPassword) // Should be here for every OP system. If I created a buggy wallet on OSX and transfered it to other system, it should also work.
};
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- possiblePasswords.Add(StringCutIssue(originalPassword));
- }
-
return possiblePasswords.ToArray();
}
@@ -31,5 +35,104 @@ namespace WalletWasabi.Helpers
var myString = Encoding.UTF8.GetString(bytes.Take(text.Length).ToArray());
return text.Substring(0, myString.Length);
}
+
+ public static bool IsTooLong(string password, out string limitedPassword)
+ {
+ limitedPassword = password;
+ if (password is null)
+ {
+ return false;
+ }
+
+ if (IsTooLong(password.Length))
+ {
+ limitedPassword = password.Substring(0, MaxPasswordLength);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool IsTooLong(int length)
+ {
+ return length > MaxPasswordLength;
+ }
+
+ public static bool IsTrimable(string password, out string trimmedPassword)
+ {
+ trimmedPassword = password;
+ if (password is null)
+ {
+ return false;
+ }
+
+ var beforeTrim = password.Length;
+
+ trimmedPassword = password.Trim();
+
+ if (beforeTrim != trimmedPassword.Length)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool TryPassword(KeyManager keyManager, string password, out string compatibilityPasswordUsed)
+ {
+ compatibilityPasswordUsed = null;
+ try
+ {
+ GetMasterExtKey(keyManager, password, out compatibilityPasswordUsed);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public static ExtKey GetMasterExtKey(KeyManager keyManager, string password, out string compatiblityPassword)
+ {
+ if (IsTooLong(password, out _)) // Password should be formatted, before entering here.
+ {
+ throw new FormatException(PasswordTooLongMessage);
+ }
+
+ if (IsTrimable(password, out _)) // Password should be formatted, before entering here.
+ {
+ throw new FormatException("Leading and trailing white spaces are not enabled!");
+ }
+
+ compatiblityPassword = null;
+
+ Exception resultException = null;
+
+ foreach (var pw in GetPossiblePasswords(password))
+ {
+ try
+ {
+ ExtKey result = keyManager.GetMasterExtKey(pw);
+
+ if (resultException != null) // Now the password is OK but if we had SecurityException before than we used a cmp password.
+ {
+ compatiblityPassword = pw;
+ Logger.LogError<KeyManager>(CompatibilityPasswordWarnMessage);
+ }
+ return result;
+ }
+ catch (SecurityException ex) // Any other exception - let it go.
+ {
+ resultException = ex;
+ }
+ }
+
+ if (resultException is null) // This mustn't be null.
+ {
+ throw new InvalidOperationException();
+ }
+
+ throw resultException; // Throw the last exception - Invalid password.
+ }
}
}

MODIFY
WalletWasabi/KeyManagement/KeyManager.cs
WalletWasabi/KeyManagement/KeyManager.cs
@@ -76,8 +76,6 @@ namespace WalletWasabi.KeyManagement
public const int AbsoluteMinGapLimit = 21;
- public bool IsCompatibilityPasswordUsed { get; private set; }
-
[JsonConstructor]
public KeyManager(BitcoinEncryptedSecretNoEC encryptedSecret, byte[] chainCode, HDFingerprint? masterFingerprint, ExtPubKey extPubKey, bool? passwordVerified, int? minGapLimit, BlockchainState blockchainState, string filePath = null, KeyPath accountKeyPath = null)
{
@@ -554,7 +552,6 @@ namespace WalletWasabi.KeyManagement
public ExtKey GetMasterExtKey(string password)
{
- IsCompatibilityPasswordUsed = false;
if (password is null)
{
password = "";
@@ -565,14 +562,9 @@ namespace WalletWasabi.KeyManagement
throw new SecurityException("This is a watchonly wallet.");
}
- Exception resultException = null;
-
- foreach (var pw in PasswordHelper.GetPossiblePasswords(password))
- {
try
{
- Key secret = EncryptedSecret.GetKey(pw);
-
+ Key secret = EncryptedSecret.GetKey(password);
var extKey = new ExtKey(secret, ChainCode);
// Backwards compatibility:
@@ -581,21 +573,12 @@ namespace WalletWasabi.KeyManagement
MasterFingerprint = secret.PubKey.GetHDFingerPrint();
}
- if (IsCompatibilityPasswordUsed)
- {
- Logger.LogError<KeyManager>(Constants.CompatibilityPasswordWarnMessage);
- }
return extKey;
}
catch (SecurityException ex)
{
- resultException = ex;
- }
-
- IsCompatibilityPasswordUsed = true; // The first iteration will try the original password if we get here it was failed.
+ throw new SecurityException("Invalid password.", ex);
}
-
- throw new SecurityException("Invalid password.", resultException);
}
public bool TestPassword(string password)

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -1033,7 +1033,6 @@ namespace WalletWasabi.Services
Script customChange = null,
IEnumerable<TxoRef> allowedInputs = null)
{
- password = password ?? ""; // Correction.
toSend = Guard.NotNullOrEmpty(nameof(toSend), toSend);
if ((feeRate is null && feeTarget is null)

Code refactor - pw separation