Skip to content

Implement fee savings and privacy metrics for payjoin outcomes#2

Open
0xZaddyy wants to merge 1 commit intopayjoin:masterfrom
0xZaddyy:privacy-metrics
Open

Implement fee savings and privacy metrics for payjoin outcomes#2
0xZaddyy wants to merge 1 commit intopayjoin:masterfrom
0xZaddyy:privacy-metrics

Conversation

@0xZaddyy
Copy link
Contributor

This PR implements a fee savings calculation model for payjoin transactions, also adding a privacy metrics to payjoin outcome scoring

Copy link
Contributor

@Mshehu5 Mshehu5 left a comment

Choose a reason for hiding this comment

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

Nice to see you contributing to this !

just have a couple of niits and questions

Comment on lines +244 to +267
let base_savings_sats = 150.0;

// Larger amounts might justify slightly higher fee savings due to more inputs
// Cap at 2x for very large amounts
let amount_factor = (amount / 100000.0).min(2.0);
let total_savings = (base_savings_sats * (1.0 + amount_factor * 0.2)) as u64;

Amount::from_sat(total_savings)
}

/// Calculate privacy score for a payjoin
/// Higher scores indicate better privacy benefits
fn calculate_payjoin_privacy_score(amount: f64) -> f64 {
// Base privacy benefit from transaction structure obfuscation
let base_privacy = 10.0;

// Larger amounts get slightly higher privacy scores as they're more valuable to hide
// Log scaling, capped at 1.0
let amount_factor = (amount / 100000.0).ln_1p().min(1.0);

// Random timing component (simplified - in reality depends on network timing)
let timing_privacy = 2.0;

base_privacy + (amount_factor * 5.0) + timing_privacy
Copy link
Contributor

Choose a reason for hiding this comment

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

niit : Since some of these are hardcode maybe const world be better
e.g

+const BASE_PRIVACY_SCORE: f64 = 10.0;
+const TIMING_PRIVACY_BONUS: f64 = 2.0;

also the numbers being multiplied and divided having a const for them with a variable name can help know the usecase better

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the review @Mshehu5

privacy_score: f64,
}

impl InitiatePayjoinOutcome {
Copy link
Contributor

Choose a reason for hiding this comment

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

We now have fee_savings and privacy_score calculation in InitiatePayjoinOutcome should this be done for RespondToPayjoinOutcome so scoring is not affected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, RespondToPayjoinOutcome should have the same privacy_score as InitiatePayjoinOutcome to ensure fair comparison between initiating vs responding to payjoins. since both actions provides identical fee savings and privacy benefits.

Comment on lines +134 to 137
let score = base_score + fee_benefit + privacy_benefit;
debug!("InitiatePayjoinEvent score: {:?} (base: {:?}, fee: {:?}, privacy: {:?})",
score, base_score, fee_benefit, privacy_benefit);
ActionScore(score)
Copy link
Contributor

@Mshehu5 Mshehu5 Jan 26, 2026

Choose a reason for hiding this comment

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

This question is for anyone reviewing

if maybe a simulation where fee_benefit is meant to matter in. (maybe to see the fee saving benefits of transaction cut through in multi party)
Should it be scaled up or have an option of scaling it up? with a utility factor or something else
as currently with this score, base_score will mostly be higher and be the main decider due to payjoin_utility_factor increase

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. Fee's right now are static.
One TODO item is to pay a higher fee rate the more anxious you are for your deadline. This should be modeled as a piecewise linear with a cap. Also this should probably be implemented first in the unilateral strategy then else where.

Copy link
Collaborator

@arminsabouri arminsabouri left a comment

Choose a reason for hiding this comment

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

Thanks for picking up a hard ticket for your first PR in this repo!

  • The privacy score stuff we can come back to once we start work on subset sum density. For now I would just remove that code and its TODO.
  • Left some comments about the fee savings logic.

I would encourage you to stick with this !

Comment on lines -109 to +110
// TODO: somekind of privacy gained metric?
/// Privacy score based on input/output mixing and timing analysis resistance
privacy_score: f64,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking back at this privacy score comment now. I think its a bit outdated and should be removed of 2 party payjoins. Eventually we want dense subset score. i.e given I payjoin with this UTXO over others how dense is subtransaction model. Or given I do a mp pj with this participant over others how dense is the subtransaction model.

Comment on lines +246 to +248
// Larger amounts might justify slightly higher fee savings due to more inputs
// Cap at 2x for very large amounts
let amount_factor = (amount / 100000.0).min(2.0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure I understand this comment. A fee a user would pay is a reflection of their impatience not really a function of the payment amount.

let amount_factor = (amount / 100000.0).min(2.0);
let total_savings = (base_savings_sats * (1.0 + amount_factor * 0.2)) as u64;

Amount::from_sat(total_savings)
Copy link
Collaborator

Choose a reason for hiding this comment

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

For context: fees are largely ignored by the simulation right now. If you look in the coin selection code you see that fee rate is hardcoded and the unilateral txs all have the same "weight" so they are paying the same absolute fee.

The fee savings for a 2-party payjoin is mainly for the responder / receiver. Where they only need to cover the fees of a input and output contribution and the rest is covered by the sender.

Ideally the fee rate a agent choose is a function of their impatience (how close to the deadline they are). This should be a TODO -- I dont think we have this documented anywhere.

An easier to place to start is probably representing fee savings to the batched unilateral strategy. Where an agent make 1 tx instead of N when they know about N payment obligation

In general fee savings should always reflect how much blockspace you saved. For the payjoin receiver they would compare the absolute fee they would have to pay in a unilateral Tx ( something that we can hardcode for now given that all txs weight and pay the same fee rate) vs the absolute fee they would have to contribute to just adding an input and output.

Comment on lines +134 to 137
let score = base_score + fee_benefit + privacy_benefit;
debug!("InitiatePayjoinEvent score: {:?} (base: {:?}, fee: {:?}, privacy: {:?})",
score, base_score, fee_benefit, privacy_benefit);
ActionScore(score)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. Fee's right now are static.
One TODO item is to pay a higher fee rate the more anxious you are for your deadline. This should be modeled as a piecewise linear with a cap. Also this should probably be implemented first in the unilateral strategy then else where.

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.

3 participants