ViewModels run on Avalonia.Headless: wizard flows, password and file-lock handling, seed and private-key reveal gating, contacts, settings, sync and send against the fake server. The CLI entry point is invoked in-process end to end. Localization checks cover the 7 languages, placeholders and keys used in sources. Internal seams (UpdateCheck, ProfileOverride) keep the tests offline.
213 lines
8.0 KiB
C#
213 lines
8.0 KiB
C#
using PalladiumWallet.App.Localization;
|
|
using PalladiumWallet.App.ViewModels;
|
|
using PalladiumWallet.Core.Chain;
|
|
using PalladiumWallet.Core.Crypto;
|
|
using PalladiumWallet.Core.Storage;
|
|
using PalladiumWallet.Core.Wallet;
|
|
|
|
namespace PalladiumWallet.Tests.App;
|
|
|
|
/// <summary>
|
|
/// Secret-handling gates in the UI layer: the seed and private keys must stay hidden until the
|
|
/// wallet password is supplied, and must be hidden again on close. Also settings, wallet info
|
|
/// and the remaining wizard flows.
|
|
/// </summary>
|
|
[Collection("AppPaths")]
|
|
public sealed class MainWindowViewModelSecurityTests : VmTestBase
|
|
{
|
|
[Fact]
|
|
public Task Seed_StaysHidden_UntilTheRightPasswordIsGiven() => UiSession.Run(() =>
|
|
{
|
|
var vm = new MainWindowViewModel();
|
|
RestoreFlow(vm, Mnemonic, "pw", "s1");
|
|
vm.OpenWalletInfoCommand.Execute(null);
|
|
Assert.True(vm.WalletInfoHasSeed);
|
|
Assert.True(vm.WalletInfoSeedNeedsPassword);
|
|
Assert.Equal("", vm.WalletInfoSeedText);
|
|
|
|
vm.WalletInfoSeedPasswordInput = "nope";
|
|
vm.RevealSeedCommand.Execute(null);
|
|
Assert.False(vm.IsWalletInfoSeedRevealed);
|
|
Assert.Equal(Loc.Tr("msg.wrongpassword"), vm.WalletInfoSeedError);
|
|
Assert.Equal("", vm.WalletInfoSeedText);
|
|
|
|
vm.WalletInfoSeedPasswordInput = "pw";
|
|
vm.RevealSeedCommand.Execute(null);
|
|
Assert.True(vm.IsWalletInfoSeedRevealed);
|
|
Assert.Equal(Mnemonic, vm.WalletInfoSeedText);
|
|
|
|
vm.CloseWalletInfoCommand.Execute(null); // closing wipes the revealed state
|
|
Assert.False(vm.IsWalletInfoSeedRevealed);
|
|
Assert.Equal("", vm.WalletInfoSeedText);
|
|
});
|
|
|
|
[Fact]
|
|
public Task WatchOnlyWallet_HasNoSeedToReveal() => UiSession.Run(() =>
|
|
{
|
|
var vm = new MainWindowViewModel();
|
|
var address = HdAccount.FromMnemonic(ParseMnemonic(), null, ScriptKind.NativeSegwit, ChainProfiles.Mainnet)
|
|
.GetReceiveAddress(0).ToString();
|
|
vm.WizardStartImportAddressCommand.Execute(null);
|
|
vm.ImportAddressInput = address;
|
|
vm.WizardNextFromImportAddressCommand.Execute(null);
|
|
vm.EncryptWallet = false;
|
|
vm.CreateOrRestoreCommand.Execute(null);
|
|
vm.OpenWalletInfoCommand.Execute(null);
|
|
|
|
Assert.False(vm.WalletInfoHasSeed);
|
|
vm.RevealSeedCommand.Execute(null);
|
|
Assert.False(vm.IsWalletInfoSeedRevealed);
|
|
});
|
|
|
|
[Fact]
|
|
public Task PrivateKey_IsRevealedOnlyAfterThePassword_AndOnlyWhenTheWalletHasKeys() => UiSession.Run(async () =>
|
|
{
|
|
var account = HdAccount.FromMnemonic(ParseMnemonic(), null, ScriptKind.NativeSegwit, ChainProfiles.Mainnet);
|
|
WireFundedChain(account.GetReceiveAddress(0), 1_000_000);
|
|
var vm = new MainWindowViewModel();
|
|
RestoreFlow(vm, Mnemonic, "pw", "pk");
|
|
await Eventually(() => !vm.IsSyncing && vm.Addresses.Count > 0);
|
|
|
|
var row = vm.Addresses.First(a => !a.IsChange && a.Indice == 0);
|
|
Assert.True(row.HasPrivKey);
|
|
vm.ShowAddressInfo(row);
|
|
|
|
vm.RequestPrivKeyRevealCommand.Execute(null);
|
|
Assert.True(vm.IsPrivKeyPromptOpen);
|
|
Assert.False(vm.IsAddressPrivKeyRevealed);
|
|
|
|
vm.PrivKeyPromptPassword = "wrong";
|
|
vm.ConfirmPrivKeyPasswordCommand.Execute(null);
|
|
Assert.False(vm.IsAddressPrivKeyRevealed);
|
|
Assert.Equal(Loc.Tr("msg.wrongpassword"), vm.PrivKeyPromptError);
|
|
|
|
vm.PrivKeyPromptPassword = "pw";
|
|
vm.ConfirmPrivKeyPasswordCommand.Execute(null);
|
|
Assert.True(vm.IsAddressPrivKeyRevealed);
|
|
Assert.False(vm.IsPrivKeyPromptOpen);
|
|
|
|
vm.CloseAddressInfoCommand.Execute(null); // closing the sheet re-hides everything
|
|
vm.ShowAddressInfo(row);
|
|
Assert.False(vm.IsAddressPrivKeyRevealed);
|
|
});
|
|
|
|
[Fact]
|
|
public Task WatchOnlyAddressRows_NeverCarryAPrivateKey() => UiSession.Run(async () =>
|
|
{
|
|
var account = HdAccount.FromMnemonic(ParseMnemonic(), null, ScriptKind.NativeSegwit, ChainProfiles.Mainnet);
|
|
WireFundedChain(account.GetReceiveAddress(0), 1_000_000);
|
|
var vm = new MainWindowViewModel();
|
|
vm.WizardStartImportXkeyCommand.Execute(null);
|
|
vm.ImportXkeyInput = account.ToSlip132();
|
|
vm.WizardNextFromImportXkeyCommand.Execute(null);
|
|
vm.EncryptWallet = false;
|
|
vm.CreateOrRestoreCommand.Execute(null);
|
|
await Eventually(() => !vm.IsSyncing && vm.Addresses.Count > 0);
|
|
|
|
Assert.All(vm.Addresses, a => Assert.False(a.HasPrivKey));
|
|
vm.ShowAddressInfo(vm.Addresses[0]);
|
|
vm.RequestPrivKeyRevealCommand.Execute(null);
|
|
Assert.False(vm.IsAddressPrivKeyRevealed);
|
|
Assert.False(vm.IsPrivKeyPromptOpen);
|
|
});
|
|
|
|
[Fact]
|
|
public Task Units_AndLanguages_AreAppliedAndPersisted() => UiSession.Run(() =>
|
|
{
|
|
var vm = new MainWindowViewModel();
|
|
|
|
vm.SetUnitCommand.Execute("sat");
|
|
Assert.True(vm.IsUnitSat);
|
|
Assert.Equal("sat", vm.UnitLabel);
|
|
Assert.Equal("sat", AppConfig.Load(AppPaths.ConfigPath()).Unit);
|
|
|
|
foreach (var lang in Loc.Languages)
|
|
{
|
|
vm.SetLanguageCommand.Execute(lang);
|
|
Assert.Equal(lang, vm.Loc.Language);
|
|
}
|
|
Assert.True(vm.IsLangZh);
|
|
Assert.False(vm.IsLangIt);
|
|
});
|
|
|
|
[Fact]
|
|
public Task SettingsFromDisk_AreRestoredByANewViewModel() => UiSession.Run(() =>
|
|
{
|
|
new MainWindowViewModel().SetLanguageCommand.Execute("de");
|
|
|
|
var vm2 = new MainWindowViewModel();
|
|
|
|
Assert.Equal("de", vm2.Loc.Language);
|
|
Assert.True(vm2.IsLangDe);
|
|
});
|
|
|
|
[Fact]
|
|
public Task Wizard_BackNavigation_ReturnsToThePreviousStep() => UiSession.Run(() =>
|
|
{
|
|
var vm = new MainWindowViewModel();
|
|
vm.WizardStartRestoreCommand.Execute(null);
|
|
vm.MnemonicInput = Mnemonic;
|
|
vm.WizardNextFromWordsCommand.Execute(null);
|
|
Assert.True(vm.IsStepPassphrase);
|
|
|
|
vm.WizardBackCommand.Execute(null);
|
|
Assert.True(vm.IsStepWords);
|
|
vm.WizardBackCommand.Execute(null);
|
|
Assert.True(vm.IsStepStart);
|
|
});
|
|
|
|
[Fact]
|
|
public Task ImportWif_CreatesAnImportedKeyWallet_AndRejectsGarbage() => UiSession.Run(() =>
|
|
{
|
|
var key = new NBitcoin.Key();
|
|
var wif = key.GetWif(PalladiumNetworks.Mainnet).ToString();
|
|
var vm = new MainWindowViewModel();
|
|
|
|
vm.WizardStartImportWifCommand.Execute(null);
|
|
vm.ImportWifInput = "definitely-not-a-wif";
|
|
vm.WizardNextFromImportWifCommand.Execute(null);
|
|
Assert.True(vm.IsStepImportWif);
|
|
|
|
vm.ImportWifInput = wif;
|
|
vm.WizardNextFromImportWifCommand.Execute(null);
|
|
vm.EncryptWallet = true;
|
|
vm.PasswordInput = vm.ConfirmPasswordInput = "pw";
|
|
vm.CreateOrRestoreCommand.Execute(null);
|
|
|
|
Assert.True(vm.IsWalletOpen);
|
|
Assert.Contains("imported", vm.NetworkInfo);
|
|
var stored = WalletStore.Load(Assert.Single(AppPaths.WalletFiles(NetKind.Mainnet)), "pw");
|
|
Assert.Equal(wif, Assert.Single(stored.WifKeys!));
|
|
});
|
|
|
|
[Fact]
|
|
public Task ImportXprv_CreatesASpendableWallet() => UiSession.Run(() =>
|
|
{
|
|
var account = HdAccount.FromMnemonic(ParseMnemonic(), null, ScriptKind.NativeSegwit, ChainProfiles.Mainnet);
|
|
var vm = new MainWindowViewModel();
|
|
vm.WizardStartImportXkeyCommand.Execute(null);
|
|
vm.ImportXkeyInput = account.ToSlip132Private();
|
|
vm.WizardNextFromImportXkeyCommand.Execute(null);
|
|
vm.EncryptWallet = false;
|
|
vm.CreateOrRestoreCommand.Execute(null);
|
|
|
|
Assert.True(vm.IsWalletOpen);
|
|
Assert.False(vm.IsWatchOnlyAccount);
|
|
});
|
|
|
|
[Fact]
|
|
public Task ClosingTheWallet_ReleasesTheLock_SoItCanBeReopened() => UiSession.Run(() =>
|
|
{
|
|
var vm = new MainWindowViewModel();
|
|
RestoreFlow(vm, Mnemonic, "pw", "relock");
|
|
var path = Assert.Single(AppPaths.WalletFiles(NetKind.Mainnet));
|
|
Assert.Null(WalletLock.TryAcquire(path)); // held while open
|
|
|
|
vm.CloseWalletCommand.Execute(null);
|
|
|
|
using var l = WalletLock.TryAcquire(path); // released on close
|
|
Assert.NotNull(l);
|
|
Assert.False(vm.IsWalletOpen);
|
|
});
|
|
}
|