Skip to content

qt: preserve and display consumed PQC usage in transaction signing flows - #167

Merged
kiwidream merged 18 commits into
1.x.xfrom
kiwidream/preserve-usage-in-qt-transactions
Sep 17, 2026
Merged

kiwidream merged 18 commits into
1.x.xfrom
kiwidream/preserve-usage-in-qt-transactions

Conversation

@kiwidream

@kiwidream kiwidream commented Sep 16, 2026

Copy link
Copy Markdown
Member

Summary

A transaction-signing attempt can consume PQC signature capacity and then fail, or succeed through a path that discarded its usage report. The Qt dialogs dropped that report, so the wallet silently lost track of capacity that had already been spent.

This adds a shared transaction formatter, qt/pqcusageformat, that renders a usage report as the overall state, every affected key with its counter, limit, remaining capacity and limit state, and every usage warning. An empty report formats to nothing, so an absent report is never presented as zero consumption. An outcome sentence distinguishes a consuming success from a failure after consumption.

  • Send. A failed preparation keeps its original reason and severity and appends the attempt's usage before the prepared transaction is discarded. The confirmation and success warning paths are unchanged.
  • PSBT. The dialog uses the shared formatter, and failure outcomes now read as failures rather than plain consumption.
  • Fee bump. Signing failure, commit failure and success all present the attempt's usage. The error boxes are plain text, so backend error strings are shown literally, and the success message precedes the state reset. Every presentation path re-checks the model and the attempt generation after each modal, so a model destroyed during presentation changes no further state.
  • Backend transport. createTransaction in wallet/interfaces.cpp assigns the usage report before its error return, so a normal signing failure still reports the capacity it reserved.

Portable transactions and PSBTs are unchanged: usage stays wallet-local.

Refs #141.

Testing

  • Built locally.
  • Ran focused unit or functional tests for the changed area.
  • Ran lint or formatting checks relevant to this change.
  • Not run. Reason:

Linux (Qt 6.4.2, GCC 13.3) before the rebase, and re-run on macOS after it. RelWithDebInfo in both cases.

On macOS the GUI suites must be run with QT_QPA_PLATFORM=cocoa; under the default minimal platform AppTests and WalletTests report as passing without executing (0 ms). The results below are from the cocoa run.

  • cmake --build build --target test_bitcoin-qt and the full test_qbit-qt suite under Xvfb/xcb: all suites pass, including WalletTests 3/0 and PSBTOperationsDialogTests 13/0 with the new usageStatesOnSuccessAndFailure slot. The same base is all-pass without this change.
  • test_qbit -t 'wallet_tests,feebumper_tests,wallet_p2mr_parallel_signing_tests,spend_tests': 62 cases, 1214 assertions, pass.
  • Deliberate regressions were applied one at a time, rebuilt, run and restored, and each produced a named failing assertion: dropping the Send usage argument, gating usage on warnings, dropping a key or the remaining-capacity field in the formatter, discarding the report before presentation, each of the three fee-bump paths, the fixture's assignment boundary in all three flows, reusing a stale report, dropping warnings, emitting the success message after feeBumped, forcing the informational severity, rendering the error box as auto-detected rich text, and adding usage metadata to a portable PSBT or transaction or replacing a failed PSBT. Reverting only the interfaces.cpp reorder fails the new real-wallet regression with usage.key_states.size() == 1U ... [0 != 1], after a check proves the durable counter was already reserved.
  • Lint in Docker (ci/lint_imagefile): Success: no issues found in 356 source files.

Target Branch

  • This PR targets main or a maintainer-requested release branch such as 0.1.x.

Targets 1.x.x.

#161 has landed, so #146 is the only remaining dependency. The branch is rebased onto current 1.x.x with #146 merged in, so until #146 lands, "Files changed" against 1.x.x includes its content as well.

For review, use the #141-only diff: 831e01e6...89bb2f05 — 14 files, one commit. That base is also pushed as kiwidream/integration-base-146 if a branch ref is easier to work with. Once #146 lands, the 1.x.x diff collapses to exactly those 14 files on its own.

Risk / Review Notes

  • Consensus, script, crypto, wallet, P2P, release, CI, or security-sensitive behavior changed.
  • No consensus, script, crypto, wallet, P2P, release, CI, or security-sensitive behavior changed.

Notes:

Wallet-facing presentation plus one interface-transport reorder. This is stacked on #146's asynchronous fee-bump transport. The review base is 831e01e615, which is current 1.x.x with #146 merged.

A follow-up for cross-dialog consistency is tracked in #166: the sign/verify message dialog still formats limit states with its own local helper and untranslatable enum names. That dialog is out of scope here.

Operation generations, cancellation boundaries, unlock lifetime and worker-thread cleanup are preserved. Portable transactions and PSBTs keep their exact bytes; the new tests assert that directly. Exception paths below Qt still lose usage, as documented previously; this change does not widen them. The external-signer route reports nothing, so no hardware usage is ever claimed.

Docs / Process Impact

  • I updated public docs because this PR changes user-visible behavior, integration guidance, release/process guidance, or expected validation.
  • No public docs update needed. Reason: this restores consumed-capacity reporting inside existing transaction-signing dialogs; it adds no new workflow or portable format.

libbitcoinpqc Subtree Checklist (if src/libbitcoinpqc changed)

Not applicable; the subtree is untouched.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-16T21:12:10.076725Z 2e1fd96 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kiwidream
kiwidream changed the base branch from kiwidream/integration-base-146-161 to 1.x.x September 16, 2026 14:40
@kiwidream kiwidream closed this Sep 16, 2026
@kiwidream kiwidream reopened this Sep 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83ceebb1ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/qt/walletmodel.cpp
# Conflicts:
#	src/qt/test/apptests.cpp
A transaction-signing attempt can consume PQC signature capacity and then
fail, or succeed through a path that discarded its usage report. The Qt
dialogs dropped that report, so the wallet silently lost track of capacity
that had already been spent.

Add a shared transaction formatter, qt/pqcusageformat, that renders a usage
report as the overall state, every affected key with its counter, limit,
remaining capacity and limit state, and every usage warning. An empty report
formats to nothing, so an absent report is never presented as zero
consumption, and an outcome sentence distinguishes a consuming success from
a failure after consumption.

Send preparation failures now keep their original reason and severity and
append the attempt's usage before the prepared transaction is discarded.
The PSBT dialog uses the shared formatter and reports failure outcomes as
failures. Fee-bump signing failure, commit failure and success all present
the attempt's usage; the error boxes are plain text, so backend error strings
are shown literally, and the success message precedes the state reset. The
presentation paths re-check the model and the attempt generation after every
modal, so a model destroyed during presentation changes no further state.

Assign the usage report in the wallet interface's createTransaction before
its error return, so a normal signing failure still reports the capacity it
reserved.

Portable transactions and PSBTs are unchanged: usage stays wallet-local.

Refs #141.
@kiwidream
kiwidream force-pushed the kiwidream/preserve-usage-in-qt-transactions branch from 83ceebb to 89bb2f0 Compare September 16, 2026 20:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89bb2f0568

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/qt/walletmodel.cpp Outdated
WalletModel::bumpFeePrepared presents several modal dialogs, each of which
runs a nested event loop. Unloading the wallet during one of those loops
destroys the model, and every path then returned into the destroyed object:
the prepare-failure and draft-failure boxes and the copied-PSBT message
called resetBumpFeeState(), the fee bump confirmation read
m_bump_fee_generation and the cancellation state, and the unlock path wrote
m_bump_fee_unlock_context. Each one is a use-after-free.

Apply the guard bumpFeeFinished already uses: hold a QPointer to the model,
and check it together with the attempt generation after every nested loop
before touching a member or emitting a signal. The short-circuit keeps the
member read from happening once the model is gone.

The unlock context is acquired across such a loop, so hold it in a local
until the model is known to have survived and only then hand it over. A
destroyed model cannot take ownership, and dropping the context releases the
unlock rather than leaking it into freed memory.

requestUnlock() itself runs the passphrase dialog in a nested loop and then
reads the encryption status, so it reports an invalid context when the model
did not survive. Its other callers already treat an invalid context as a
failed unlock, so they stop reading freed memory too.

Add a regression test per guarded loop, destroying the model from inside the
dialog. Under an address sanitizer build, removing any one guard reports a
heap-use-after-free at that site and the tests fail; with the guards in place
the suite passes.
@kiwidream
kiwidream merged commit 4bb6462 into 1.x.x Sep 17, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant