Skip to content

Trustworthy sign_psbt amount/fee display for non-default sighash#512

Open
iartemov-ledger wants to merge 5 commits into
developfrom
negative_fee
Open

Trustworthy sign_psbt amount/fee display for non-default sighash#512
iartemov-ledger wants to merge 5 commits into
developfrom
negative_fee

Conversation

@iartemov-ledger

@iartemov-ledger iartemov-ledger commented Jun 25, 2026

Copy link
Copy Markdown
Contributor
sighash_proposal_changes_2

See the seequences and screens for different sighash combinations :

@codecov-commenter

codecov-commenter commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.39%. Comparing base (560df9a) to head (d543499).

Files with missing lines Patch % Lines
src/handler/lib/policy.c 50.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #512      +/-   ##
===========================================
+ Coverage    72.36%   72.39%   +0.02%     
===========================================
  Files           71       71              
  Lines        10347    10385      +38     
  Branches      1876     1888      +12     
===========================================
+ Hits          7488     7518      +30     
- Misses        2113     2118       +5     
- Partials       746      749       +3     
Flag Coverage Δ
unittests 72.39% <88.88%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

elf sizes
source = source branch negative_fee
target = target branch develop

Device .text source .text target .text delta .bss source .bss target .bss delta max stack size source max stack size target max stack size delta
apex_p 104104 102568 1536 9686 9658 28 31268 31296 -28
nanos2 83060 81524 1536 8580 8552 28 32376 32404 -28
nanox 82532 81252 1280 6808 6780 28 8192 8192 0
flex 108457 106921 1536 9686 9658 28 27172 27200 -28
stax 107895 106359 1536 9686 9658 28 27172 27200 -28

Stack consumption summary

⚠️ This summary is for informative purpose only. It may not give the application actual worst case, for example if the test coverage is low.

Device Worst case (bytes) Remaining stack (bytes) Test
apex_p 10221 21047 test_e2e_miniscript.py::test_e2e_miniscript_me_large_vault[apex_p-apex_p]
flex 10221 16951 test_e2e_miniscript.py::test_e2e_miniscript_me_large_vault[flex-flex]
nanosp 10221 22155 test_e2e_miniscript.py::test_e2e_miniscript_me_large_vault[nanosp-nanosp]
nanox 10221 11639 test_e2e_miniscript.py::test_e2e_miniscript_me_large_vault[nanox-nanox]
stax 10221 16951 test_e2e_miniscript.py::test_e2e_miniscript_me_large_vault[stax-stax]

Full details

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR makes the on-device sign_psbt transaction amount/fee display trustworthy under non-default sighash rules by showing only information that the signed inputs provably commit to (full details for default sighash, “spent only” for ANYONECANPAY|ALL, or a notice-only screen when outputs/amounts can’t be trusted).

Changes:

  • Introduces commit predicates and decide_tx_display_mode() to determine which transaction details are safe to display given the effective sighash commitments.
  • Updates PSBT signing UX to support FULL / SPENT_ONLY / UNAVAILABLE display modes and to scope negative-fee rejection + high-fee warnings to cases where fee is meaningful.
  • Adds unit tests for the new predicates/mode decision and extends Python integration tests for negative-fee behavior under ANYONECANPAY vs default sighash.

Reviewed changes

Copilot reviewed 11 out of 252 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
unit-tests/test_sighash.c Adds unit tests for sighash commit predicates and display-mode decision logic.
tests/test_sign_psbt_with_sighash_types.py Imports IncorrectDataError and adds tests for negative-fee handling under ANYONECANPAY vs default sighash.
tests/conftest.py Makes Nano settings navigation more robust by matching the start of the confirm button label text.
src/ui/display.h Introduces tx_summary_t and threads display-mode state through UI transaction validation structs/APIs.
src/ui/display.c Adds prepare_tx_summary() and updates simplified/streaming UI entrypoints to consume tx_summary_t.
src/ui/display_nbgl.c Implements new NBGL review labeling/flow for SPENT_ONLY and UNAVAILABLE modes and gates high-fee warning on FULL.
src/handler/sign_psbt/transaction_display.c Computes total_spent, decides display mode, and updates UI invocation + high-fee logic accordingly (including notice-only path).
src/handler/sign_psbt/preprocess_outputs.c Restricts negative-fee rejection to fully-committed transactions only.
src/handler/sign_psbt/preprocess_inputs.c Aggregates “inputs open/outputs open” flags over signed inputs and tracks internal input totals.
src/handler/sign_psbt.h Extends signing state with internal_inputs_total_amount and aggregated sighash openness flags.
src/common/sighash.h Adds commit predicate helpers, tx_display_mode_t, and decide_tx_display_mode().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_sign_psbt_with_sighash_types.py
Comment thread tests/test_sign_psbt_with_sighash_types.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 254 changed files in this pull request and generated 1 comment.

Comment thread src/ui/display.h
@iartemov-ledger iartemov-ledger marked this pull request as ready for review July 3, 2026 12:32
@iartemov-ledger iartemov-ledger requested a review from a team as a code owner July 3, 2026 12:32
@iartemov-ledger iartemov-ledger requested a review from bigspider July 3, 2026 12:33

@bigspider bigspider left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great initiative, I think this is going in a good direction.

As discussed offline, there are a few things that might deserve further thinking:

  • It seems hopeless to try to provide a meaningful display when different inputs have different signing flags; but it is unclear if there are use cases for that. We could either forbid or automatically consider unsafe any such scenario (TBD), and focus on cases where all inputs have the same sighash flag (TL;DR: no sighash flag mixing)
  • SIGHASH_SINGLE actually seems safe in the 1-input-1-output case, and since there are already deployed usecases for it, it might be worth including it
  • The UX in the TX_DISPLAY_SPENT_ONLY case is not very clear, as it doesn't clearly distinguish between: (a) the amount spent/received from/to the wallet policy; (b) the amount going to each output. This needs to be clearly for the user.
  • When not signing with SIGHASH_ALL, but with no sighash flag mixing, it might be worth clearly showing what's the sighash flag used.

For anything not fitting these patterns, it is probably fine to fall back to TX_DISPLAY_UNAVAILABLE and just admit that we can't clear-sign it. Maybe the warning could be worded to be more explicitly scarier.

Possible add-ons that are fine to defer to future PRs:

  • Even in the TX_DISPLAY_UNAVAILABLE case, if a certain registered wallet policy is involved in a transaction
  • An advanced transaction mode showing info also about each input (e.g.: if internal, its address, its amount, the sighash flag requested for signing if non-default).

I think only small changes are needed to fill these gaps, and it will make the display for such transactions a lot more meaningful.

Comment thread src/common/sighash.h Outdated
Comment on lines +56 to +67
// False iff ANYONECANPAY is set (then others can add inputs, so the total input
// amount, and the fee, is unknown). Only valid for accepted sighash types.
static inline bool sighash_commits_all_inputs(uint32_t sighash_type) {
return (sighash_type & SIGHASH_ANYONECANPAY) == 0;
}

// True only for base-type ALL (ALL, or DEFAULT on taproot); NONE/SINGLE leave the
// outputs (and our change) free to change after signing.
static inline bool sighash_commits_all_outputs(uint32_t sighash_type) {
uint32_t base = sighash_type & ~(uint32_t) SIGHASH_ANYONECANPAY;
return base == SIGHASH_ALL || base == SIGHASH_DEFAULT;
}

@bigspider bigspider Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think what we care about in these functions is: does the signature commit to all the inputs/outputs that are provided in the PSBT that we are signing? That is because we base the display info based on the provided PSBT. We don't necessarily care if other inputs/outputs are added by someone else, as long as it doesn't invalidate what we committed to.

Therefore, we might generalize these functions a bit:

  • if there is only 1 input, then sighash_commits_all_inputs could always return true. All the sighash flags commit to the current input, whether or not ANYONECANPAY is present.
  • if there is only 1 input and output, then SIGHASH_SINGLE still commits to that all outputs, as trivially there is only one.

In particular, this would allow covering the SINGLE|ACP case among the 'safer' patterns (the one-input-one-output case is interesting as it is already used in some protocols). Note that we still don't know the fee in this case (and someone else might be paying for it!), but we do know exactly how much we're spending/receiving.

@iartemov-ledger iartemov-ledger Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Generalizing sighash_commits_all_inputs() to return true for a single input would be unsafe, because this function isn't just informational - it feeds sighash_inputs_open, which drives the negative-fee check skip in preprocess_outputs().

The subtlety is that two different ideas are being mixed up here:

  • "commits to the inputs provided in the PSBT" - yes, true for 1 input
  • "the input set is closed, i.e. no one can add inputs" = !ANYONECANPAY - what this function actually means

A single ANYONECANPAY input satisfies the first but not the second: others can still append inputs. If sighash_commits_all_inputs() returned true there, sighash_inputs_open would become false, the negative-fee check would no longer be skipped, and a valid 1-input ANYONECANPAY maker PSBT (output > our input) would be wrongly rejected.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sure, the negative-fee check should be only done when the fee can be computed, which is basically only the case for SIGHASH_ALL/DEFAULT. Other than that, I don't think there is any other problem?

@iartemov-ledger iartemov-ledger marked this pull request as draft July 8, 2026 16:46
@iartemov-ledger iartemov-ledger force-pushed the negative_fee branch 2 times, most recently from 9c19708 to e2d58b6 Compare July 10, 2026 08:56
@github-actions

Copy link
Copy Markdown
Contributor

Code coverage report

Code Coverage

Per-file coverage
Package Line Rate Branch Rate Health
src.boilerplate 100% 0%
src.common 85% 68%
src 87% 59%
src.debug-helpers 100% 0%
src.handler 85% 83%
src.handler.lib 64% 59%
src.handler.sign_psbt 88% 83%
src.musig 24% 15%
src.ui 86% 100%
Summary 75% (3046 / 4070) 63% (1635 / 2600)

@iartemov-ledger

iartemov-ledger commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Normally the change-requests expressed all along the PR work have been addressed or postponned:

sign_psbt non-default-sighash — change requests & resolution

Branch: negative_fee (vs develop). "Done" = implemented in this PR; "Postponed" = out of scope, tracked for a follow-up.

Change request How it's addressed in the PR
Trustworthy amount/fee display for non-default sighash Done — 3 review modes: FULL / NET_ONLY / UNAVAILABLE, per what the signature really commits to
[#512] Mixed sighash across signed inputs can't be shown coherently Done — mixed sighash → UNAVAILABLE
[#512] A non-default sighash must never show a trusted fee Done — never reaches FULL; shows the net "You spend/receive" + "Fees: Not available" instead
[#512] SINGLE / SINGLE|ANYONECANPAY at 1-input / 1-output (coinjoin maker/taker) Done — shown as NET_ONLY, not UNAVAILABLE
[#354] Sign when output amount > input amount (open input set), incl. PayJoin net-receive Done — accepted when the set is open (ACP/NONE/SINGLE), no dummy input; net-receive shown as "You receive". Default sighash still rejects a genuine negative fee
[#520 ] PayJoin display for default SIGHASH_ALL — "You receive" + total value of external inputs Postponed — follow-up PR
Show the effective sighash on the review Done — "Signing rule" row (ACP | ALL, SINGLE, Mixed, …)
Show the account (with dynamic direction) in a trust-reduced review Done — account row in NET_ONLY and UNAVAILABLE, from our internal inputs: default → derived label (e.g. Native SegWit #0), registered → its name; From when spending, To when receiving, neutral when unknown

@iartemov-ledger iartemov-ledger marked this pull request as ready for review July 10, 2026 09:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

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.

4 participants