Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions tests/test_psbt_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,104 @@ def test_verify_multisig_output(self):



def test_multisig_no_descriptor_all_outputs_are_spend(self):
cases = [
(PSBTTestData.MULTISIG_NATIVE_SEGWIT_1_INPUT, PSBTTestData.MULTISIG_NATIVE_SEGWIT_CHANGE),
(PSBTTestData.MULTISIG_NESTED_SEGWIT_1_INPUT, PSBTTestData.MULTISIG_NESTED_SEGWIT_CHANGE),
(PSBTTestData.MULTISIG_LEGACY_P2SH_1_INPUT, PSBTTestData.MULTISIG_LEGACY_P2SH_CHANGE),
]

fee_amount = 5_000
for psbt_base64, change_data in cases:
psbt: PSBT = PSBT.parse(a2b_base64(psbt_base64))
input_amount = sum(inp.utxo.value for inp in psbt.inputs)
output_amount = input_amount - fee_amount

stripped = create_output(change_data, output_amount)
stripped.witness_script = None
stripped.redeem_script = None
psbt.outputs.append(stripped)

psbt_parser = PSBTParser(p=psbt, seed=self.seed, network=SettingsConstants.REGTEST)

assert psbt_parser.num_change_outputs == 0, \
"Without witness_script/redeem_script the output policy cannot match; no change should be detected"
assert psbt_parser.num_destinations == 1, \
"The unverifiable output must fall through to destination_addresses"
assert psbt_parser.spend_amount == output_amount, \
"The full output value is treated as a spend when the multisig script is absent"
assert psbt_parser.change_amount == 0, \
"No output can be verified as change without the multisig script"
assert psbt_parser.fee_amount == fee_amount
assert psbt_parser.input_amount == psbt_parser.spend_amount + psbt_parser.change_amount + psbt_parser.fee_amount


def test_consolidation_all_outputs_internal(self):
cases = [
(
PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_1_INPUT,
PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_CHANGE,
PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_SELF_TRANSFER,
),
(
PSBTTestData.SINGLE_SIG_NESTED_SEGWIT_1_INPUT,
PSBTTestData.SINGLE_SIG_NESTED_SEGWIT_CHANGE,
PSBTTestData.SINGLE_SIG_NESTED_SEGWIT_SELF_TRANSFER,
),
(
PSBTTestData.SINGLE_SIG_TAPROOT_1_INPUT,
PSBTTestData.SINGLE_SIG_TAPROOT_CHANGE,
PSBTTestData.SINGLE_SIG_TAPROOT_SELF_TRANSFER,
),
(
PSBTTestData.SINGLE_SIG_LEGACY_P2PKH_1_INPUT,
PSBTTestData.SINGLE_SIG_LEGACY_P2PKH_CHANGE,
PSBTTestData.SINGLE_SIG_LEGACY_P2PKH_SELF_TRANSFER,
),
(
PSBTTestData.MULTISIG_NATIVE_SEGWIT_1_INPUT,
PSBTTestData.MULTISIG_NATIVE_SEGWIT_CHANGE,
PSBTTestData.MULTISIG_NATIVE_SEGWIT_SELF_TRANSFER,
),
(
PSBTTestData.MULTISIG_NESTED_SEGWIT_1_INPUT,
PSBTTestData.MULTISIG_NESTED_SEGWIT_CHANGE,
PSBTTestData.MULTISIG_NESTED_SEGWIT_SELF_TRANSFER,
),
(
PSBTTestData.MULTISIG_LEGACY_P2SH_1_INPUT,
PSBTTestData.MULTISIG_LEGACY_P2SH_CHANGE,
PSBTTestData.MULTISIG_LEGACY_P2SH_SELF_TRANSFER,
),
]

fee_amount = 5_000
for psbt_base64, change_data, self_transfer_data in cases:
psbt: PSBT = PSBT.parse(a2b_base64(psbt_base64))
input_amount = sum(inp.utxo.value for inp in psbt.inputs)
total_output = input_amount - fee_amount
# Exact split does not matter; both outputs must be recognised as internal
change_output_amount = total_output // 2
self_transfer_amount = total_output - change_output_amount

psbt.outputs.append(create_output(change_data, change_output_amount))
psbt.outputs.append(create_output(self_transfer_data, self_transfer_amount))

psbt_parser = PSBTParser(p=psbt, seed=self.seed, network=SettingsConstants.REGTEST)

assert psbt_parser.num_destinations == 0, \
"All outputs belong to the signing wallet; no external recipients expected"
assert psbt_parser.num_change_outputs == 2, \
"Both change-branch (1/*) and receive-branch (0/*) outputs must be classified as internal"
assert psbt_parser.spend_amount == 0, \
"No value is sent to an external address in a full consolidation"
assert psbt_parser.change_amount == total_output, \
"All output value (minus fee) must be retained internally"
assert psbt_parser.fee_amount == fee_amount
assert psbt_parser.input_amount == psbt_parser.spend_amount + psbt_parser.change_amount + psbt_parser.fee_amount



# TODO: Refactor all tests to be in the TestPSBTParser class(?)
def test_p2tr_change_detection():
""" Should successfully detect change in a p2tr to p2tr psbt spend
Expand Down