Skip to content
Bitcoin Roots

v30.3-roots.1 · Commit 50: [PATCH 50/68] qt: cleanse and release transient sweep cache

src/qt/sweepdialog.cpp

Before: src/qt/sweepdialog.cpp · After: src/qt/sweepdialog.cpp

File metadata
index c410a97daa2643469ab2a149605b2ae48d09a3fd..4cdfcb7e5d350a1e1327ec5ee9dd199494361ec6 100644

Download original patch · Release notes

Diff rows 1–97 of 97. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

src/qt/sweepdialog.cpp CHANGED
@@ -36,7 +36,8 @@ public:
36
  SecureString result{encoded.constData(), static_cast<size_t>(encoded.size())};
37
  if (!encoded.isEmpty()) memory_cleanse(encoded.data(), encoded.size());
38
  // QLineEdit does not expose its internal buffer for cleansing. Replace
39
- // its contents before clearing so the widget no longer retains the WIF.
 
40
  setText(QString(text().size(), QChar{' '}));
41
  clear();
42
  return result;
@@ -118,7 +119,7 @@ SweepDialog::SweepDialog(WalletModel* wallet_model, QWidget* parent)
118
  m_request_generation = 0;
119
  const QSignalBlocker block{m_private_key};
120
  m_private_key->clear();
121
- m_private_key_cache.clear();
122
  m_preview_txid.clear();
123
  setBusy(false);
124
  m_preview_current = false;
@@ -130,6 +131,15 @@ SweepDialog::SweepDialog(WalletModel* wallet_model, QWidget* parent)
130
 
131
  SweepDialog::~SweepDialog() { clearSensitive(); }
132
 
 
 
 
 
 
 
 
 
 
133
  void SweepDialog::invalidate()
134
  {
135
  if (m_request_generation != 0 && !m_wallet_model->invalidateSweepRequests()) {
@@ -137,7 +147,7 @@ void SweepDialog::invalidate()
137
  return;
138
  }
139
  m_request_generation = 0;
140
- m_private_key_cache.clear();
141
  m_preview_txid.clear();
142
  m_preview_current = false;
143
  m_broadcast->setEnabled(false);
@@ -162,6 +172,7 @@ void SweepDialog::setResult(const QString& text, bool can_broadcast)
162
 
163
  void SweepDialog::preview()
164
  {
 
165
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
166
  if (!IsValidDestination(destination)) { setResult(tr("Enter a valid destination address."), false); return; }
167
  const QSignalBlocker block{m_private_key};
@@ -170,7 +181,11 @@ void SweepDialog::preview()
170
  setBusy(true);
171
  m_result->setText(tr("Scanning for eligible coins and preparing a preview…"));
172
  m_request_generation = m_wallet_model->requestSweep(m_private_key_cache, destination, false);
173
- if (m_request_generation == 0) setResult(tr("Another sweep broadcast is already being submitted."), false);
 
 
 
 
174
  }
175
 
176
  void SweepDialog::broadcast()
@@ -182,12 +197,20 @@ void SweepDialog::broadcast()
182
  if (answer != QMessageBox::Yes) return;
183
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
184
  const auto expected_txid{Txid::FromHex(m_preview_txid.toStdString())};
185
- if (!expected_txid) { setResult(tr("The preview is no longer valid. Create a fresh preview."), false); return; }
 
 
 
 
186
  setBusy(true);
187
  m_preview_current = false;
188
  m_result->setText(tr("Rechecking the preview and broadcasting the sweep…"));
189
  m_request_generation = m_wallet_model->requestSweep(m_private_key_cache, destination, true, expected_txid);
190
- if (m_request_generation == 0) setResult(tr("Sweep broadcast submission has already started. Wait for its result."), false);
 
 
 
 
191
  }
192
 
193
  void SweepDialog::reject()
@@ -211,7 +234,7 @@ void SweepDialog::handleSweepFinished(uint64_t generation, bool broadcast, bool
211
  setResult(broadcast
212
  ? tr("Broadcast failed: %1. Create a fresh preview before trying again.").arg(error)
213
  : tr("Preview failed: %1").arg(error), false);
214
- m_private_key_cache.clear();
215
  m_preview_txid.clear();
216
  return;
217
  }
@@ -237,7 +260,7 @@ void SweepDialog::clearSensitive()
237
  m_request_generation = 0;
238
  const QSignalBlocker block{m_private_key};
239
  m_private_key->clear();
240
- m_private_key_cache.clear();
241
  m_preview_txid.clear();
242
  m_preview_current = false;
243
  setBusy(false);
 
36
  SecureString result{encoded.constData(), static_cast<size_t>(encoded.size())};
37
  if (!encoded.isEmpty()) memory_cleanse(encoded.data(), encoded.size());
38
  // QLineEdit does not expose its internal buffer for cleansing. Replace
39
+ // its visible contents before clearing; Qt does not guarantee physical
40
+ // erasure of every internal QString or undo-buffer copy.
41
  setText(QString(text().size(), QChar{' '}));
42
  clear();
43
  return result;
 
119
  m_request_generation = 0;
120
  const QSignalBlocker block{m_private_key};
121
  m_private_key->clear();
122
+ clearPrivateKeyCache();
123
  m_preview_txid.clear();
124
  setBusy(false);
125
  m_preview_current = false;
 
131
 
132
  SweepDialog::~SweepDialog() { clearSensitive(); }
133
 
134
+ void SweepDialog::clearPrivateKeyCache()
135
+ {
136
+ // clear() alone retains storage, including the old secret beyond size().
137
+ // Wipe live bytes first, including short-string storage, then release any
138
+ // allocation through secure_allocator's cleansing deallocator.
139
+ if (!m_private_key_cache.empty()) memory_cleanse(m_private_key_cache.data(), m_private_key_cache.size());
140
+ SecureString{}.swap(m_private_key_cache);
141
+ }
142
+
143
  void SweepDialog::invalidate()
144
  {
145
  if (m_request_generation != 0 && !m_wallet_model->invalidateSweepRequests()) {
 
147
  return;
148
  }
149
  m_request_generation = 0;
150
+ clearPrivateKeyCache();
151
  m_preview_txid.clear();
152
  m_preview_current = false;
153
  m_broadcast->setEnabled(false);
 
172
 
173
  void SweepDialog::preview()
174
  {
175
+ clearPrivateKeyCache();
176
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
177
  if (!IsValidDestination(destination)) { setResult(tr("Enter a valid destination address."), false); return; }
178
  const QSignalBlocker block{m_private_key};
 
181
  setBusy(true);
182
  m_result->setText(tr("Scanning for eligible coins and preparing a preview…"));
183
  m_request_generation = m_wallet_model->requestSweep(m_private_key_cache, destination, false);
184
+ if (m_request_generation == 0) {
185
+ clearPrivateKeyCache();
186
+ setBusy(false);
187
+ setResult(tr("Another sweep broadcast is already being submitted."), false);
188
+ }
189
  }
190
 
191
  void SweepDialog::broadcast()
 
197
  if (answer != QMessageBox::Yes) return;
198
  const CTxDestination destination{DecodeDestination(m_destination->text().toStdString())};
199
  const auto expected_txid{Txid::FromHex(m_preview_txid.toStdString())};
200
+ if (!expected_txid) {
201
+ clearPrivateKeyCache();
202
+ setResult(tr("The preview is no longer valid. Create a fresh preview."), false);
203
+ return;
204
+ }
205
  setBusy(true);
206
  m_preview_current = false;
207
  m_result->setText(tr("Rechecking the preview and broadcasting the sweep…"));
208
  m_request_generation = m_wallet_model->requestSweep(m_private_key_cache, destination, true, expected_txid);
209
+ if (m_request_generation == 0) {
210
+ clearPrivateKeyCache();
211
+ setBusy(false);
212
+ setResult(tr("Sweep broadcast submission has already started. Wait for its result."), false);
213
+ }
214
  }
215
 
216
  void SweepDialog::reject()
 
234
  setResult(broadcast
235
  ? tr("Broadcast failed: %1. Create a fresh preview before trying again.").arg(error)
236
  : tr("Preview failed: %1").arg(error), false);
237
+ clearPrivateKeyCache();
238
  m_preview_txid.clear();
239
  return;
240
  }
 
260
  m_request_generation = 0;
261
  const QSignalBlocker block{m_private_key};
262
  m_private_key->clear();
263
+ clearPrivateKeyCache();
264
  m_preview_txid.clear();
265
  m_preview_current = false;
266
  setBusy(false);