Skip to content

Conversation

@Mshehu5
Copy link
Contributor

@Mshehu5 Mshehu5 commented Nov 21, 2025

Replaces: #880 as contibutor is currently busy

I aslo followed comments from previous PR: #880 (comment)

closes: #788

Previously, P2TR weight estimation assumed key-path spends only, which was incorrect for script-path spends and non-default sighash.
This change derives weight from actual witness data when available, matching P2WSH handling.
When witness data is unavailable, applications must provide expected_weight manually, ensuring accurate fee calculation.

Also Adding new helpers for P2tr so key-spend inputs get the default weight and script-path inputs take caller-supplied weight.

Pull Request Checklist

Please confirm the following before requesting review:

@coveralls
Copy link
Collaborator

coveralls commented Nov 21, 2025

Pull Request Test Coverage Report for Build 20275523512

Details

  • 85 of 86 (98.84%) changed or added relevant lines in 3 files are covered.
  • 1 unchanged line in 1 file lost coverage.
  • Overall coverage increased (+0.1%) to 83.402%

Changes Missing Coverage Covered Lines Changed/Added Lines %
payjoin/src/core/receive/mod.rs 76 77 98.7%
Files with Coverage Reduction New Missed Lines %
payjoin/src/core/psbt/mod.rs 1 67.38%
Totals Coverage Status
Change from base Build 20245341060: 0.1%
Covered Lines: 9728
Relevant Lines: 11664

💛 - Coveralls

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.

Approach looks correct. Had a suggestion and question for breaking up p2tr key spend and script spend input pair constructors. Did not review the tests. Will circle back once this is out of draft. Thanks

Comment on lines 239 to 253
match witness.len() {
0 => Err(InputWeightError::NotSupported),
1 => {
let signature =
witness.iter().next().ok_or(InputWeightError::NotSupported)?;
if !(signature.len() == SCHNORR_SIGNATURE_SIZE
|| signature.len() == SCHNORR_SIGNATURE_SIZE + 1)
{
return Err(InputWeightError::NotSupported);
}
Ok(InputWeightPrediction::new(0, vec![signature.len()]))
}
_ => Ok(InputWeightPrediction::new(
0,
witness.iter().map(|el| el.len()).collect::<Vec<_>>(),
Copy link
Collaborator

@arminsabouri arminsabouri Nov 24, 2025

Choose a reason for hiding this comment

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

Is this match on length necessary? Once we have a non-empty witness can we not just calculate expected satisfaction weight?

 InputWeightPrediction::new(0, witness.iter().map(|el| el.len()).collect::<Vec<_>>())

@arminsabouri
Copy link
Collaborator

@Mshehu5 I'll do another review once this is out of draft

@Mshehu5 Mshehu5 marked this pull request as ready for review December 9, 2025 22:00
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.

Some trivial nits in the tests and suggested clean up of duplicated code. The main issue remaining is a possible regression in psbt/mod.rs

Ok(iwp)
},
P2tr => Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH),
P2tr => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe there is a regression here. For key spends we know the weight prediction even though we do not have the witness. We may need to internally distinguish between keyspends and scriptpath using the new ctors introduced by this PR.

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 and for sharing the links to help clarify the necessary changes.

I’ve pushed updates that set the weight to key spend when the witness is not available.
Script path weights can still be set explicitly using the constructor or derived from witness

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe this will have the same issue as the original issue. I.e if the witness is not known and its a taproot output we assume the key spend path is being used -- even though that may not be the case; which is what prompted the new ctors and user provided weight.

Perphaps the best way to solve this is to set expected weight to the key spend weight in the key spend ctor. And return a error here as we cannot accurately determine the input weight prediction for a taproot output that has no witness. What do you think?

Comment on lines 1359 to 1364
let expect_weight = psbtin
.witness_utxo
.as_ref()
.filter(|txo| txo.script_pubkey.is_p2tr())
.map(|_| Weight::from_wu(P2TR_INPUT_WEIGHT));
InputPair::new(txin, psbtin, expect_weight).expect("Input pair should be valid")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you explain this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is no longer needed as key spend is now automatically derived when no witness is present unlike before

Ok(iwp)
},
P2tr => Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH),
P2tr => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe this will have the same issue as the original issue. I.e if the witness is not known and its a taproot output we assume the key spend path is being used -- even though that may not be the case; which is what prompted the new ctors and user provided weight.

Perphaps the best way to solve this is to set expected weight to the key spend weight in the key spend ctor. And return a error here as we cannot accurately determine the input weight prediction for a taproot output that has no witness. What do you think?

@Mshehu5 Mshehu5 force-pushed the P2TR_estimation branch 2 times, most recently from a4c1812 to ce2be55 Compare December 15, 2025 20:11
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.

Cack
I had a few nits. Otherwise looks good 👍
Thanks

Comment on lines 212 to 213
let key_spend_weight =
InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH.weight() + NON_WITNESS_INPUT_WEIGHT;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: this could be a const.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

when trying to do this I get an error from rust analyzer:

const P2TR_KEY_SPEND_WEIGHT: Weight = InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH.weight() + NON_WITNESS_INPUT_WEIGHT;

cannot call non-const operator in constants
calls in constants are limited to constant functions, tuple structs and tuple variants

weight( ) is a non constant function

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant the whole expressions can be evaluated and expressed as a const
const DEAFULT_SIGHASH_KEY_SPEND_INPUT_WEIGHT: Weight = ...,

Previously, P2TR weight estimation assumed key-path spends only, which was incorrect for script-path spends and non-default sighash.

This change derives weight from actual witness data when available, matching P2WSH handling.

When witness data is unavailable, applications must provide expected_weight manually, ensuring accurate fee calculation.
@Mshehu5 Mshehu5 force-pushed the P2TR_estimation branch 2 times, most recently from 6368515 to ab2f027 Compare December 16, 2025 16:21
Use the new helpers so key-spend inputs get the default weight and
script-path inputs take caller-supplied weight.
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.

Re-AcK a7dbe56

@arminsabouri arminsabouri merged commit 4c91534 into payjoin:master Dec 16, 2025
9 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.

Incorrectly estimating taproot satisfiability weights

4 participants