Skip to content

Add antiprobing check to reject payjoin inputs already offered in a prior session - #863

Open
Sandipmandal25 wants to merge 1 commit into
masterfrom
feat/anti-probing-inputs-seen
Open

Add antiprobing check to reject payjoin inputs already offered in a prior session#863
Sandipmandal25 wants to merge 1 commit into
masterfrom
feat/anti-probing-inputs-seen

Conversation

@Sandipmandal25

@Sandipmandal25 Sandipmandal25 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Track every PSBT input outpoint offered during a PayJoin session in a dedicated redb table.
  • Reject new PayJoin attempts when any selected input has already been offered to a receiver.
  • Fall back to a normal send when reused inputs are detected.

Why

Without this check, a malicious receiver could probe which UTXOs belong to the sender by initiating multiple PayJoin sessions and observing which inputs reappear.

Offered inputs are never removed, even after fallback or cancellation, because the receiver has already seen them.

Checklist

resolves #779

Summary by CodeRabbit

  • Security

    • Payjoin now prevents transaction inputs from being offered more than once, helping protect against input-probing attempts.
    • Previously offered inputs remain tracked across restarts and new sessions.
  • Bug Fixes

    • Added validation to reject Payjoin requests containing previously used inputs.
    • Ensured offered inputs are recorded reliably before a session is saved.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 75fe8286-37b6-40c8-93ba-53154a5954f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Payjoin sender construction now persistently tracks offered transaction outpoints, rejects reused inputs before proceeding, records accepted inputs before session persistence, and tests behavior across persister instances.

Changes

Payjoin anti-probing

Layer / File(s) Summary
Persist offered inputs
rust/src/manager/wallet_manager/payjoin.rs
Adds a redb table and persister methods for checking and recording offered outpoints, with tests covering fresh, reused, and cross-instance persistence.
Enforce checks in sender construction
rust/src/manager/wallet_manager/payjoin.rs
build_sender checks signed PSBT inputs before proceeding and records them before saving the new sender session.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant build_sender
  participant PayjoinSessionPersister
  participant redb
  build_sender->>PayjoinSessionPersister: check_no_inputs_seen_before(signed_psbt inputs)
  PayjoinSessionPersister->>redb: read inputs_offered
  build_sender->>PayjoinSessionPersister: record_offered_inputs(inputs)
  PayjoinSessionPersister->>redb: persist outpoints and timestamps
  build_sender->>PayjoinSessionPersister: save sender session
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an anti-probing check for reused Payjoin inputs.
Description check ✅ Passed The description covers the summary, motivation, and checklist, but omits testing and platform coverage details.
Linked Issues check ✅ Passed The changes satisfy #779 by persisting offered outpoints, checking reuse before building Payjoin, and aborting safely.
Out of Scope Changes check ✅ Passed No unrelated or extra-scope changes are apparent beyond the Payjoin anti-probing behavior and its tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/anti-probing-inputs-seen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Greptile Summary

Adds persistent PayJoin input tracking and rejects previously offered outpoints before constructing a new sender session.

  • Introduces a per-wallet redb table keyed by transaction outpoint.
  • Records selected inputs before sender construction and session persistence.
  • Adds tests covering fresh, reused, and persistently recorded inputs.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
rust/src/manager/wallet_manager/payjoin.rs Adds persistent anti-probing input checks and focused persistence tests; no eligible follow-up defect remains.

Reviews (3): Last reviewed commit: "Add anti-probing check to reject payjoin..." | Re-trigger Greptile

@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@greptileai

@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@Sandipmandal25 Sandipmandal25 self-assigned this Jul 29, 2026
@Sandipmandal25 Sandipmandal25 added the SoB-2026 Project for Summer of Bitcoin 2026 label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
rust/src/manager/wallet_manager/payjoin.rs (1)

173-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repository’s error-context helpers.

Replace these map_err(|e| eyre::eyre!(...)) closures with cove_util::ResultExt::map_err_str or .map_err_prefix so redb error context is added consistently.

As per coding guidelines: “Use cove_util::ResultExt::map_err_str and .map_err_prefix instead of equivalent map_err closures that construct error variants from strings.”

Also applies to: 184-186, 207-213, 218-223

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/manager/wallet_manager/payjoin.rs` around lines 173 - 180, Replace
the string-building map_err closures in the payjoin database operations,
including begin_read and the ranges around open_table and lines 207-213 and
218-223, with cove_util::ResultExt::map_err_str or map_err_prefix. Preserve the
existing error context and special TableDoesNotExist handling while using the
repository’s ResultExt helpers consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/src/manager/wallet_manager/payjoin.rs`:
- Around line 418-422: Move persister.record_offered_inputs(&input_outpoints)
out of the pre-construction path in the payjoin flow. Ensure
build_recommended(...) completes successfully before recording inputs, then
perform the check-and-record immediately before the session’s .save(persister)
call so construction failures leave inputs reusable while save failures still
burn them.
- Around line 371-374: Make the anti-probing check and offered-input insertion
atomic in the flow around build_sender and check_no_inputs_seen_before: perform
lookup and insertion for INPUTS_OFFERED_TABLE within a single redb write
transaction, ensuring concurrent calls cannot both pass before recording. Update
the affected persistence API as needed and add a regression test that runs
concurrent build_sender calls with the same input outpoint and verifies only one
succeeds.

---

Nitpick comments:
In `@rust/src/manager/wallet_manager/payjoin.rs`:
- Around line 173-180: Replace the string-building map_err closures in the
payjoin database operations, including begin_read and the ranges around
open_table and lines 207-213 and 218-223, with cove_util::ResultExt::map_err_str
or map_err_prefix. Preserve the existing error context and special
TableDoesNotExist handling while using the repository’s ResultExt helpers
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c47ad86-7a4e-4376-aecf-5f56b913096e

📥 Commits

Reviewing files that changed from the base of the PR and between bae3022 and 6d65f90.

📒 Files selected for processing (1)
  • rust/src/manager/wallet_manager/payjoin.rs

Comment thread rust/src/manager/wallet_manager/payjoin.rs
Comment thread rust/src/manager/wallet_manager/payjoin.rs Outdated
@Sandipmandal25
Sandipmandal25 force-pushed the feat/anti-probing-inputs-seen branch from 6d65f90 to 5677334 Compare July 29, 2026 16:26
@praveenperera
praveenperera force-pushed the feat/anti-probing-inputs-seen branch from 5677334 to 18373b3 Compare August 13, 2026 20:03
@praveenperera

Copy link
Copy Markdown
Member

@Sandipmandal25 will the database get filled up? is there a clean up mechanism? is there any legit reason for the UTXOs to be offered more than once? should there be a user override?

persister: &PayjoinSessionPersister,
) -> Result<V2Sender<WithReplyKey>> {
// TODO: anti-probing (inputs_seen), verify our inputs have not appeared in a prior session
// TODO: surface payjoin downgrade to the user when the fallback tx is broadcast instead of the proposal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Sandipmandal25 we still have to do this TODO?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this todo is about surfacing payjoin downgrade to the user when the fallback tx broadcasts instead of the proposal. its a cross layer change not related to anti probing i will do this in follow up was waiting for this pr to merge.

@Sandipmandal25

Copy link
Copy Markdown
Collaborator Author

@Sandipmandal25 will the database get filled up? is there a clean up mechanism? is there any legit reason for the UTXOs to be offered more than once? should there be a user override?

table grows some bytes per input per payjoin attempt, storage is not a concern. no cleanup by design once a receiver has seen ourinputs they can probe again on retry so removing entries reopens the attack. legit reuse is a valid concern though if a payjoin times out the user would want to retry with the same UTXOs. i can add a user override or scope the block to sessions that actually reached the directory. which do you prefer?

@praveenperera
praveenperera force-pushed the feat/anti-probing-inputs-seen branch 2 times, most recently from 89d8169 to 18373b3 Compare August 20, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

SoB-2026 Project for Summer of Bitcoin 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Payjoin sender: implement anti probing check

2 participants