Skip to content

Improve Sender Input Validation Tests and Error Types - #1836

Open
Jolah1 wants to merge 2 commits into
payjoin:masterfrom
Jolah1:validate-sender-input-utxos
Open

Jolah1 wants to merge 2 commits into
payjoin:masterfrom
Jolah1:validate-sender-input-utxos

Conversation

@Jolah1

@Jolah1 Jolah1 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Part of #678.

What

Check that every original sender outpoint is still present in the proposal, in its original order, before classifying any proposal input as the receiver's. Existing receiver-input tests now modify the proposal's actual receiver input instead of the sender's.

Why

Master already rejects proposals with missing or shuffled sender inputs, but only after the input loop. An altered sender outpoint is classified as a receiver input first, so the sender sees an unrelated error ("an input in proposed transaction belonging to the receiver is not finalized"). Checking ordering up front makes the error name the actual problem.

The UTXO-data check was dropped per review; that part of #678 remains open.

Tests

cargo test -p payjoin ordered_subsequence --all-features
cargo test -p payjoin test_sender_input_outpoint_changed --all-features
cargo test -p payjoin test_receiver_input_ --all-features
cargo test -p payjoin --all-features
cargo fmt --all -- --check
cargo clippy -p payjoin --all-targets --all-features -- -D warnings
codespell

Disclosure: code and PR text drafted with AI assistance (Codex, Claude); reviewed and tested by me.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MRG6vHFsY9Ny88eRtTQ2vz

@Jolah1
Jolah1 marked this pull request as ready for review August 24, 2026 13:40
@coveralls

coveralls commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 34593798280

Coverage increased (+0.02%) to 86.669%

Details

  • Coverage increased (+0.02%) from the base build.
  • Patch coverage: 47 of 47 lines across 1 file are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 16578
Covered Lines: 14368
Line Coverage: 86.67%
Coverage Strength: 341.27 hits per line

💛 - Coveralls

@caarloshenriq caarloshenriq 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.

Concept ACK d576e05

The security logic is sound. The outpoint pre-check correctly uses a single advancing iterator with any() to enforce that all original outpoints appear as an ordered subsequence in the proposal, catching modified outpoints early instead of misclassifying them as receiver inputs. The UTXO validation with is_none_or correctly accepts stripped fields (per BIP 78) while rejecting modified or injected data. The fix to the existing receiver-input tests (operating on input[1] instead of mangling input[0]) is a genuine improvement: those tests now exercise the actual receiver input.

Two commit hygiene points:

This commit does three things: outpoint pre-check, UTXO data validation, and existing test fixes. Per project conventions each logical unit should be its own commit. At minimum the test fixes should be separate, since they're independently correct and useful regardless of the new validation.

The commit message has no body. For a security-relevant change to PSBT validation, it would be worth explaining the threat model (receiver modifying UTXO data to invalidate signatures or cause the sender to sign a different transaction). The context in #678 covers this well and the body could reference it.

These tests mangled the first input's outpoint so that the sender's own
input would be classified as receiver-contributed, then asserted the
receiver checks against it. The proposal already carries a real receiver
input at index 1, so the setup was both indirect and never exercised the
input it claimed to cover.

Point the assertions at input 1 and drop the outpoint mangling. The
checks under test are unchanged; only the input they run against is.
@Jolah1
Jolah1 force-pushed the validate-sender-input-utxos branch from d576e05 to bd2186a Compare August 25, 2026 03:50
@Jolah1

Jolah1 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Concept ACK d576e05

The security logic is sound. The outpoint pre-check correctly uses a single advancing iterator with any() to enforce that all original outpoints appear as an ordered subsequence in the proposal, catching modified outpoints early instead of misclassifying them as receiver inputs. The UTXO validation with is_none_or correctly accepts stripped fields (per BIP 78) while rejecting modified or injected data. The fix to the existing receiver-input tests (operating on input[1] instead of mangling input[0]) is a genuine improvement: those tests now exercise the actual receiver input.

Two commit hygiene points:

This commit does three things: outpoint pre-check, UTXO data validation, and existing test fixes. Per project conventions each logical unit should be its own commit. At minimum the test fixes should be separate, since they're independently correct and useful regardless of the new validation.

The commit message has no body. For a security-relevant change to PSBT validation, it would be worth explaining the threat model (receiver modifying UTXO data to invalidate signatures or cause the sender to sign a different transaction). The context in #678 covers this well and the body could reference it.

Thanks — split into three commits: the test fixes first (they pass on master's validation logic unchanged, so they stand alone as you said), then the outpoint pre-check, then the UTXO data validation with its error variant.
Each builds and passes cargo test --all-features + clippy independently; verified the three together reproduce the original tree exactly. Commit bodies now cover the threat model for each change and reference #678. Also
rebased onto current master while I was rewriting.

@caarloshenriq caarloshenriq 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.

tACK bd2186a

All sender-input validation tests (test_sender_input_outpoint_changed, test_sender_input_witness_utxo_changed, test_sender_input_non_witness_utxo_changed) and the corrected receiver-input tests pass.

xstoicunicornx
xstoicunicornx previously approved these changes Sep 2, 2026

@xstoicunicornx xstoicunicornx 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.

utACK bd2186a.

PR addresses some deficiencies in accurate error reporting when the sender is validating the payjoin proposal. None of these changes are absolutely necessary but do improve the clarity of our tests and error reporting.

Provided some small feedback.

Comment thread payjoin/src/core/send/mod.rs Outdated
Comment thread payjoin/src/core/send/mod.rs Outdated
@Jolah1

Jolah1 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

utACK bd2186a.

PR addresses some deficiencies in accurate error reporting when the sender is validating the payjoin proposal. None of these changes are absolutely necessary but do improve the clarity of our tests and error reporting.

Provided some small feedback.

updated bd2186a6c62fb2: extracted is_ordered_subsequence with a unit test, split the UTXO check into two ensures, and added a test for unchanged/stripped data. Both folded into their existing commits, so the three-commit structure is unchanged. Re-ACK when you have a moment.

Comment thread payjoin/src/core/send/mod.rs Outdated
InternalProposalError::SenderTxinNonAllSighashType,
)?;
}
// BIP 78 lets the receiver strip these fields, but never alter them.

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.

BIP 78 doesn't currently say anything about these fields, we are enforcing this arbitrarily. Which I think is fine, but thinking about it more would like others to weigh in too.

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.

Agreed, this is policy, not spec, and the comment now says so. It also doesn't close a hole: restore_original_utxos overwrites these fields two lines after the check, so the sender signs its own values either way. The gain is a named error instead of a silent overwrite. Happy to drop this commit if the preference is status quo.

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.

Hmm... yes could we please drop this commit for now? Feel free to open as an issue to follow up on this question of whether we should enforce this policy even if not in the specification. Careful, I think some updates for the Reject proposals missing original sender inputs commit got entangled in this commit.

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.

Hmm... yes could we please drop this commit for now? Feel free to open as an issue to follow up on this question of whether we should enforce this policy even if not in the specification. Careful, I think some updates for the Reject proposals missing original sender inputs commit got entangled in this commit.

Dropped, and moved the is_ordered_subsequence test cases back into the outpoint commit.

Comment thread payjoin/src/core/send/mod.rs
Comment thread payjoin/src/core/send/mod.rs Outdated
Comment thread payjoin/src/core/send/mod.rs Outdated
@Jolah1
Jolah1 force-pushed the validate-sender-input-utxos branch from 6c62fb2 to 6db2b1d Compare September 3, 2026 18:53
Input classification treats any proposed input whose outpoint does not
match an original one as receiver-contributed. A receiver that alters an
original outpoint therefore escapes the sender-side checks entirely: the
input is validated as though the receiver had contributed it, and the
sender only learns something is wrong later, from a finalization error
that points at the wrong thing.

Check up front that every original outpoint is still present in the
proposal, in its original relative order, before classifying any input.
is_ordered_subsequence enforces that with a single advancing iterator,
so dropped and reordered inputs are both rejected as
MissingOrShuffledInputs. Keeping it a free function lets the ordering
rule be tested directly, without a proposal vector carrying several
sender inputs.

See payjoin#678.
@Jolah1
Jolah1 force-pushed the validate-sender-input-utxos branch from 6db2b1d to 283663e Compare September 11, 2026 11:23
@Jolah1 Jolah1 changed the title Validate sender input UTXO data Reject altered sender outpoints before input checks Sep 11, 2026

@xstoicunicornx xstoicunicornx 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.

utACK 283663e

Improves a few tests by removing hacks that altered sender inputs just to get them to be treated as receiver inputs and instead just references the actual receiver inputs instead. Also improves accuracy in the error type returned when there are missing or shuffled sender inputs.

@xstoicunicornx xstoicunicornx changed the title Reject altered sender outpoints before input checks Improve Sender Input Validation Tests and Error Types Sep 11, 2026
@Christopherdominic

Copy link
Copy Markdown

tACK 283663e

I traced the new check_inputs() flow and ran:

cargo test -p payjoin --lib send::test

All 40 tests passed, including the new ordered_subsequence_requires_original_order and test_sender_input_outpoint_changed tests.

The new check makes sense to me: it ensures the sender's original inputs are still present and ordered before the proposal inputs are classified, so altered sender outpoints get the appropriate MissingOrShuffledInputs error instead of being handled as receiver inputs.

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.

5 participants