Add tests to catch mutants#1622
Conversation
Coverage Report for CI Build 28409851805Coverage increased (+0.2%) to 85.673%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
| assert!(input.tap_key_sig.is_some(), "tap_key_sig should be preserved"); | ||
| assert!(!input.tap_script_sigs.is_empty(), "tap_script_sigs should be preserved"); | ||
| assert!(input.tap_merkle_root.is_some(), "tap_merkle_root should be preserved"); | ||
| } |
There was a problem hiding this comment.
Should we check the sensative fields are removed as well? Same thing for the outputs?
| assert!(input.sighash_type.is_some(), "sighash_type should be preserved"); | ||
| assert!(input.tap_key_sig.is_some(), "tap_key_sig should be preserved"); | ||
| assert!(!input.tap_script_sigs.is_empty(), "tap_script_sigs should be preserved"); | ||
| assert!(input.tap_merkle_root.is_some(), "tap_merkle_root should be preserved"); |
There was a problem hiding this comment.
Missing the final input field types that are preserved
final_script_sig: input.final_script_sig.clone(),
final_script_witness: input.final_script_witness.clone(),There was a problem hiding this comment.
Is it not odd that the receiver would keep anyting but these fields? Its essentially offloading the responsiblity of witness building to the sender. Motivating example: liana may fillout tap_merkle_root but the key spend is used. Here it would be preserved and your taptree strucuture leaked to the counter party
There was a problem hiding this comment.
Just to be clear you think we should be stripping
sighash_typetap_key_sigtap_script_sigstap_merkle_root
And just preserve these fields?
for input in &processed_psbt.inputs {
filtered_psbt.inputs.push(bitcoin::psbt::Input {
witness_utxo: input.witness_utxo.clone(),
non_witness_utxo: input.non_witness_utxo.clone(),
final_script_sig: input.final_script_sig.clone(),
final_script_witness: input.final_script_witness.clone(),
..Default::default()
});
}
There was a problem hiding this comment.
We have to do one or the other not both. The receiver either gives the sender the partial fields and the sender constructs witness OR the receiver provides fully constructed witness. The latter does break both bip174/370 definition of the finalizer role. i.e all the inputs should be finalizable. So its possible the rust-bitcoin ecosystem does not have support for this which is why we have the sender finalize today.
Thats why i found it odd that we preserve the final_ fields and the partial fields.
There was a problem hiding this comment.
According to The BIP 78 spec protocol section receiver checklist subsection, which BIP 77 references
The payjoin proposal MUST:
...
- Only finalize the inputs added by the receiver. (Referred later as
additional inputs)
There was a problem hiding this comment.
Additionally, in the sender checklist section it says:
For each input in the [payjoin] proposal:
...
Verify that no partial signature has been filled
So per the spec we want non_witness_utxo or witness_utxo and final_* fields.
b86419e to
fc33285
Compare
fc33285 to
d88c366
Compare
d88c366 to
22464e1
Compare
22464e1 to
c8a09dd
Compare
This test catches the mutation that replaces the script_pubkey match
guard in arm 3 ("our output") of PsbtContext::check_outputs with `true`.
Arm 3 matches when the proposed output has the same script_pubkey as
the original non-payee output. The mutation makes it match ANY output,
consuming original outputs incorrectly and bypassing the script check.
Following the fix in f165562 the mutants tests here were previously skipped as I had not fully grasped the logic being tested. However these mutants can now much more easily be tested following that change.
c8a09dd to
1e909d2
Compare
|
|
||
| #[test] | ||
| fn try_preserving_privacy_falls_back_when_min_in_equals_min_out() { | ||
| let original = original_from_test_vector(); | ||
| let wants_inputs = WantsOutputs::new(original, vec![0]).commit_outputs(); | ||
| let small = candidate_input_from_test_vector(Amount::ONE_SAT); | ||
| let boundary = candidate_input_from_test_vector(Amount::from_sat(2_000_000)); | ||
| let result = wants_inputs.try_preserving_privacy([small.clone(), boundary.clone()]); | ||
| assert_eq!(result.unwrap(), small); | ||
| } |
There was a problem hiding this comment.
I think this would have caught the bug BBMobile reported here. Almost on time!
| ctx.output_substitution = OutputSubstitution::Enabled; | ||
|
|
||
| let mut proposal = PARSED_PAYJOIN_PROPOSAL.clone(); | ||
| let original_change = &ctx.original_psbt.unsigned_tx.output[0]; |
There was a problem hiding this comment.
Open to having the PAYJOIN_PROPOSAL_ADDITIONALFEEOUTPUTINDEX = 0 constant?
I knew this was reality but had to go hunt down the source to make sure the test made sense.
This is a followup on payjoin#1622 to add consts for the MAX_ADDITIONAL_FEE_CONTRIBUTION and ADDITIONAL_FEE_OUTPUT_INDEX to better understand why these values are included in unit tests across the codebase.
Closes #1689
Closes #778
Pull Request Checklist
Please confirm the following before requesting review:
AI
in the body of this PR.