From 897455dab7a4f32a9cc0ff241d7a135c0627f1b8 Mon Sep 17 00:00:00 2001 From: macgyver13 <4712150+macgyver13@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:54:06 -0400 Subject: [PATCH] BIP-375: skip ineligible inputs when combining ecdh shares add fake ecdh share and dleq proof to P2SH input for valid test: two inputs using per-input ECDH shares - only eligible inputs contribute shares (P2SH excluded) remove unused return string from is_input_eligible --- bip-0375/bip375_test_vectors.json | 2 +- bip-0375/validator/inputs.py | 16 +++++++++------- bip-0375/validator/validate_psbt.py | 15 ++++----------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/bip-0375/bip375_test_vectors.json b/bip-0375/bip375_test_vectors.json index 1e390187..f6b01319 100644 --- a/bip-0375/bip375_test_vectors.json +++ b/bip-0375/bip375_test_vectors.json @@ -1011,7 +1011,7 @@ }, { "description": "can finalize: two inputs using per-input ECDH shares - only eligible inputs contribute shares (P2SH excluded)", - "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLTLcGPIp7P5Ia4ABZbNd4jlRA4dY+8e4bzsjCrJQMogIgNw2OmoWgzI3SWwuwgfMaotzFugoiSOqGCoFlHKNKDGgBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4g0iImLyaHKbt8hMVYEppdaYYYXO9TXsFJwN9g3UqSwgQBDwQAAAAAAQBTAgAAAAFA5vqmEmjxehAQHWJVNYKIKuSFi+4TNj24D98oH4Jf/AAAAAAA/////wHwSQIAAAAAABepFPRfjMomjsJuS76JkXDxOCUNfPVehwAAAAABBEdSIQKHfKAvFEBZvYLQDhs5muN094pSzvehyjfyjXiLNA0veSEDTIekSHL14fAG002IoAlZKCvUU150d8PWjDFlmR6hs5ZSrgEQBP7///8AAQMIkF8BAAAAAAABBCJRIDJt9Q/goHt6y3IHC+s7Yy65rRW6aVzB5apqoAe2FG/YAQlCAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoA2HhseneXkLLIAf3ylS54NV+0Tk4+tVtPxnldROo/OA5AA==", + "psbt": "cHNidP8B+wQCAAAAAQIEAgAAAAEEAQIBBQEBAQYBAAABDiAYpxdmOwurFLEqGncTI/8eQHndUy5d0T4o6hCBxwCYSgEPBAAAAAABAR+ghgEAAAAAABYAFCKactNKZFvTSWu79Qu7gckGP0+UIgICyBe7dSGvw16pbzv7Jw5utQ3f+lVgYnuWH+wA8pllCL9IMEUCIQCLTLcGPIp7P5Ia4ABZbNd4jlRA4dY+8e4bzsjCrJQMogIgNw2OmoWgzI3SWwuwgfMaotzFugoiSOqGCoFlHKNKDGgBAQMEAQAAACIGAsgXu3Uhr8NeqW87+ycObrUN3/pVYGJ7lh/sAPKZZQi/CAAAAIAAAAAAARAE/v///yIdAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoIQPspP8Rtyji4PYM5iIpQ6b/VbnZX2J7+amdCEvIctUKWyIeAnpIf8Gft2mHe4dC1uoYEY88TnKx6oxt5gKnrUpB2+BoQIoTs5hVRfcr1uiXFK65CbPjVKhCqbuLVs0O3tId+KGZWYsxIopJ4L1+lc4QU/fFsorLVDpocHYA486Jgi7jICEAAQ4g0iImLyaHKbt8hMVYEppdaYYYXO9TXsFJwN9g3UqSwgQBDwQAAAAAAQBTAgAAAAFA5vqmEmjxehAQHWJVNYKIKuSFi+4TNj24D98oH4Jf/AAAAAAA/////wHwSQIAAAAAABepFPRfjMomjsJuS76JkXDxOCUNfPVehwAAAAABBEdSIQKHfKAvFEBZvYLQDhs5muN094pSzvehyjfyjXiLNA0veSEDTIekSHL14fAG002IoAlZKCvUU150d8PWjDFlmR6hs5ZSrgEQBP7///8iHQJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaCECYototkRuaocCWXPZOvsZTkIKEPOhzLOK3YLUZKiUpCgiHgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEDCJBfAQAAAAAAAQQiUSAybfUP4KB7estyBwvrO2Muua0VumlcweWqaqAHthRv2AEJQgJ6SH/Bn7dph3uHQtbqGBGPPE5yseqMbeYCp61KQdvgaANh4bHp3l5CyyAH98pUueDVftE5OPrVbT8Z5XUTqPzgOQA=", "supplementary": { "inputs": [ { diff --git a/bip-0375/validator/inputs.py b/bip-0375/validator/inputs.py index d8fe3800..f36a8d4e 100644 --- a/bip-0375/validator/inputs.py +++ b/bip-0375/validator/inputs.py @@ -50,6 +50,8 @@ def collect_input_ecdh_and_pubkey( for input_map in psbt.i: input_ecdh = input_map.get_by_key(PSBT_IN_SP_ECDH_SHARE, scan_key) if input_ecdh: + if not is_input_eligible(input_map): + continue # skip ineligible inputs ecdh_point = GE.from_bytes(input_ecdh) combined_ecdh = ( ecdh_point if combined_ecdh is None else combined_ecdh + ecdh_point @@ -71,7 +73,7 @@ def pubkey_from_eligible_input(input_map: BIP375PSBTMap) -> Optional[GE]: Returns a GE point (public key), or None if not found """ - if not is_input_eligible(input_map)[0]: + if not is_input_eligible(input_map): return None # Try BIP32 derivation first (key_data is the pubkey) @@ -131,7 +133,7 @@ def _parse_non_witness_utxo(non_witness_utxo: bytes, output_index: int) -> bytes # ============================================================================ -def is_input_eligible(input_map: BIP375PSBTMap) -> Tuple[bool, str]: +def is_input_eligible(input_map: BIP375PSBTMap) -> bool: """Check if input is eligible for silent payments""" script_pubkey = _script_pubkey_from_psbt_input(input_map) assert script_pubkey is not None, ( @@ -139,7 +141,7 @@ def is_input_eligible(input_map: BIP375PSBTMap) -> Tuple[bool, str]: ) if not _has_eligible_script_type(script_pubkey): - return False, "ineligible input type" + return False NUMS_H = bytes.fromhex( "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" @@ -147,16 +149,16 @@ def is_input_eligible(input_map: BIP375PSBTMap) -> Tuple[bool, str]: if _is_p2tr(script_pubkey): tap_internal_key = input_map.get(PSBT_IN_TAP_INTERNAL_KEY) if tap_internal_key == NUMS_H: - return False, "P2TR uses NUMS point H as internal key" + return False if _is_p2sh(script_pubkey): if PSBT_IN_REDEEM_SCRIPT in input_map: redeem_script = input_map[PSBT_IN_REDEEM_SCRIPT] if not _is_p2wpkh(redeem_script): - return False, "P2SH is not P2SH-P2WPKH" + return False else: - assert False, "P2SH input missing PSBT_IN_REDEEM_SCRIPT" - return True, None + assert False + return True def _has_eligible_script_type(script_pubkey: bytes) -> bool: diff --git a/bip-0375/validator/validate_psbt.py b/bip-0375/validator/validate_psbt.py index d6c10cbc..0ec7c848 100644 --- a/bip-0375/validator/validate_psbt.py +++ b/bip-0375/validator/validate_psbt.py @@ -185,22 +185,15 @@ def validate_ecdh_coverage(psbt: PSBT) -> Tuple[bool, str]: # Verify per-input coverage for eligible inputs if scan_key_has_computed_output and not has_global_ecdh: for i, input_map in enumerate(psbt.i): - is_eligible, _ = is_input_eligible(input_map) - ecdh_share = input_map.get_by_key(PSBT_IN_SP_ECDH_SHARE, scan_key) - # Disabled this check for now since it is not strictly forbidden by BIP-375 - if not is_eligible: + if not is_input_eligible(input_map): continue - # if not is_eligible and ecdh_share: - # return ( - # False, - # f"Input {i} has ECDH share but is ineligible for silent payments", - # ) - if is_eligible and not ecdh_share: + ecdh_share = input_map.get_by_key(PSBT_IN_SP_ECDH_SHARE, scan_key) + if not ecdh_share: return ( False, f"Output script set but eligible input {i} missing ECDH share", ) - if ecdh_share: + else: # Verify per-input DLEQ proofs dleq_proof = input_map.get_by_key(PSBT_IN_SP_DLEQ, scan_key) if not dleq_proof: