fix(app): derive an address private key only when revealed

Address rows and the address detail sheet no longer carry the WIF of
every listed address for the whole session. The key is derived after the
password gate and dropped when hidden, when the sheet closes or when the
wallet closes. See ADR 0037.
This commit is contained in:
davide committed 2026-11-03 14:10:58 +01:00
1 parent 8bcee27c23
commit 06d1f59c2b
8 files changed
+119 -23

No files matched your search

@@ -77,6 +77,11 @@ public partial class MainWindowViewModel
[ObservableProperty]
private bool isAddressPrivKeyRevealed;
// WIF of the address in the open sheet, derived only after the reveal gate and dropped as
// soon as the key is hidden or the sheet closes. Never stored in rows or in AddressInfo.
[ObservableProperty]
private string revealedPrivKey = "";
// ---- private key password prompt ----
[ObservableProperty]
@@ -90,10 +95,23 @@ public partial class MainWindowViewModel
partial void OnAddressInfoChanged(AddressInfo? value)
{
IsAddressPrivKeyRevealed = false;
HidePrivKeyInternal();
ClosePrivKeyPromptInternal();
}
private void HidePrivKeyInternal()
{
IsAddressPrivKeyRevealed = false;
RevealedPrivKey = "";
}
private void RevealPrivKeyInternal()
{
if (AddressInfo is not { HasPrivKey: true } info) return;
RevealedPrivKey = KeyWif(info.IsChange, info.Index);
IsAddressPrivKeyRevealed = RevealedPrivKey.Length > 0;
}
private void ClosePrivKeyPromptInternal()
{
IsPrivKeyPromptOpen = false;
@@ -102,7 +120,8 @@ public partial class MainWindowViewModel
}
public void ShowAddressInfo(AddressRow row) =>
AddressInfo = new AddressInfo(Loc, row.Indirizzo, row.DerivPath, row.PubKey, row.PrivKey);
AddressInfo = new AddressInfo(Loc, row.Indirizzo, row.DerivPath, row.PubKey,
row.IsChange, row.Indice, row.HasPrivKey);
[RelayCommand]
private void CloseAddressInfo()
@@ -118,7 +137,7 @@ public partial class MainWindowViewModel
if (string.IsNullOrEmpty(_password))
{
// No encryption: reveal directly
IsAddressPrivKeyRevealed = true;
RevealPrivKeyInternal();
}
else
{
@@ -137,7 +156,7 @@ public partial class MainWindowViewModel
PrivKeyPromptError = Loc.Tr("msg.wrongpassword");
return;
}
IsAddressPrivKeyRevealed = true;
RevealPrivKeyInternal();
ClosePrivKeyPromptInternal();
}
@@ -145,10 +164,7 @@ public partial class MainWindowViewModel
private void CancelPrivKeyPrompt() => ClosePrivKeyPromptInternal();
[RelayCommand]
private void HideAddressPrivKey()
{
IsAddressPrivKeyRevealed = false;
}
private void HideAddressPrivKey() => HidePrivKeyInternal();
// ---- transaction detail overlay ----
@@ -265,8 +281,8 @@ public partial class MainWindowViewModel
_account.GetReceiveAddress(i).ToString(), "—", "—",
false,
_account.GetPublicKey(false, i)?.ToHex() ?? "",
KeyWif(false, i),
$"m/{_doc!.AccountPath}/0/{i}"));
$"m/{_doc!.AccountPath}/0/{i}",
!_account.IsWatchOnly));
return;
}
// Not "Confirmed - Immature - PendingVerification": those two can overlap (an
@@ -306,7 +322,7 @@ public partial class MainWindowViewModel
a.TxCount > 0 ? a.TxCount.ToString() : "—",
a.IsChange,
_account.GetPublicKey(a.IsChange, a.Index)?.ToHex() ?? "",
KeyWif(a.IsChange, a.Index),
$"m/{_doc!.AccountPath}/{(a.IsChange ? 1 : 0)}/{a.Index}"));
$"m/{_doc!.AccountPath}/{(a.IsChange ? 1 : 0)}/{a.Index}",
!_account.IsWatchOnly));
}
}
+8 -10
View File
@@ -19,21 +19,18 @@ namespace PalladiumWallet.App.ViewModels;
/// <summary>Transaction history row for the view.</summary>
public sealed record HistoryRow(string Conferma, string Importo, string Txid, bool Verified = true);
/// <summary>Address view row with pre-computed keys and derivation path.</summary>
/// <summary>
/// Address view row: public data and derivation path only. The private key is never held here;
/// it is derived on demand when the user reveals it (see <c>RevealedPrivKey</c>).
/// </summary>
public sealed record AddressRow(
string Tipo, int Indice, string Indirizzo, string Saldo, string NumTx,
bool IsChange = false, string PubKey = "", string PrivKey = "", string DerivPath = "")
{
public bool HasPrivKey => !string.IsNullOrEmpty(PrivKey);
}
bool IsChange = false, string PubKey = "", string DerivPath = "", bool HasPrivKey = false);
/// <summary>Full address data passed to the address detail overlay.</summary>
/// <summary>Address data passed to the detail overlay: derivation coordinates, never the key.</summary>
public sealed record AddressInfo(
Loc Loc,
string Address, string DerivPath, string PubKey, string PrivKey)
{
public bool HasPrivKey => !string.IsNullOrEmpty(PrivKey);
}
string Address, string DerivPath, string PubKey, bool IsChange, int Index, bool HasPrivKey);
/// <summary>Address book contact: name + blockchain address.</summary>
public sealed record ContactEntry(string Name, string Address);
@@ -286,6 +283,7 @@ public partial class MainWindowViewModel : ViewModelBase
SelectedContactInList = null;
SendToContact = null;
SelectedAddressRow = null;
AddressInfo = null; // also drops a revealed private key
IsWalletOpen = false;
IsConnected = false;
ConnectionStatus = Loc.Tr("conn.none");