mirror of
https://github.com/bitcoin/bips.git
synced 2026-04-20 16:28:39 +00:00
BIP-375: add test_runner and validate PSBT structure
Implement psbt structure checks Add test_runner.py for processing test vectors
This commit is contained in:
127
bip-0375/validator/validate_psbt.py
Normal file
127
bip-0375/validator/validate_psbt.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validates PSBTs according to BIP-375 rules
|
||||
|
||||
Provides independent checks for PSBT structure, ECDH share coverage,
|
||||
input eligibility, and output script correctness.
|
||||
"""
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
from deps.bitcoin_test.psbt import (
|
||||
PSBT,
|
||||
PSBT_GLOBAL_TX_MODIFIABLE,
|
||||
PSBT_OUT_SCRIPT,
|
||||
)
|
||||
|
||||
from .psbt_bip375 import (
|
||||
PSBT_GLOBAL_SP_ECDH_SHARE,
|
||||
PSBT_GLOBAL_SP_DLEQ,
|
||||
PSBT_IN_SP_ECDH_SHARE,
|
||||
PSBT_IN_SP_DLEQ,
|
||||
PSBT_OUT_SP_V0_INFO,
|
||||
PSBT_OUT_SP_V0_LABEL,
|
||||
)
|
||||
|
||||
|
||||
def validate_psbt_structure(psbt: PSBT) -> Tuple[bool, str]:
|
||||
"""
|
||||
Validate PSBT structure requirements
|
||||
|
||||
Checks:
|
||||
- Each output must have PSBT_OUT_SCRIPT or PSBT_OUT_SP_V0_INFO
|
||||
- PSBT_OUT_SP_V0_LABEL requires PSBT_OUT_SP_V0_INFO
|
||||
- SP_V0_INFO must be 66 bytes (33-byte scan key + 33-byte spend key)
|
||||
- ECDH shares must be 33 bytes
|
||||
- DLEQ proofs must be 64 bytes
|
||||
- TX_MODIFIABLE is zero when PSBT_OUT_SCRIPT set for SP output
|
||||
"""
|
||||
# Check output requirements
|
||||
for i, output_map in enumerate(psbt.o):
|
||||
has_script = (
|
||||
PSBT_OUT_SCRIPT in output_map and len(output_map[PSBT_OUT_SCRIPT]) > 0
|
||||
)
|
||||
has_sp_info = PSBT_OUT_SP_V0_INFO in output_map
|
||||
has_sp_label = PSBT_OUT_SP_V0_LABEL in output_map
|
||||
|
||||
# Output must have script or SP info
|
||||
if not has_script and not has_sp_info:
|
||||
return (
|
||||
False,
|
||||
f"Output {i} must have either PSBT_OUT_SCRIPT or PSBT_OUT_SP_V0_INFO",
|
||||
)
|
||||
|
||||
# SP label requires SP info
|
||||
if has_sp_label and not has_sp_info:
|
||||
return (
|
||||
False,
|
||||
f"Output {i} has PSBT_OUT_SP_V0_LABEL but missing PSBT_OUT_SP_V0_INFO",
|
||||
)
|
||||
|
||||
# Validate SP_V0_INFO field length
|
||||
if has_sp_info:
|
||||
sp_info = output_map[PSBT_OUT_SP_V0_INFO]
|
||||
if len(sp_info) != 66:
|
||||
return (
|
||||
False,
|
||||
f"Output {i} SP_V0_INFO has wrong length ({len(sp_info)} bytes, expected 66)",
|
||||
)
|
||||
|
||||
# Validate ECDH share lengths (global and per-input)
|
||||
global_ecdh_shares = psbt.g.get_all_by_type(PSBT_GLOBAL_SP_ECDH_SHARE)
|
||||
for _, ecdh_share in global_ecdh_shares:
|
||||
if len(ecdh_share) != 33:
|
||||
return (
|
||||
False,
|
||||
f"Global ECDH share has wrong length ({len(ecdh_share)} bytes, expected 33)",
|
||||
)
|
||||
|
||||
for i, input_map in enumerate(psbt.i):
|
||||
input_ecdh_shares = input_map.get_all_by_type(PSBT_IN_SP_ECDH_SHARE)
|
||||
for _, ecdh_share in input_ecdh_shares:
|
||||
if len(ecdh_share) != 33:
|
||||
return (
|
||||
False,
|
||||
f"Input {i} ECDH share has wrong length ({len(ecdh_share)} bytes, expected 33)",
|
||||
)
|
||||
|
||||
# Validate DLEQ proof lengths (global and per-input)
|
||||
global_dleq_proofs = psbt.g.get_all_by_type(PSBT_GLOBAL_SP_DLEQ)
|
||||
for _, dleq_proof in global_dleq_proofs:
|
||||
if len(dleq_proof) != 64:
|
||||
return (
|
||||
False,
|
||||
f"Global DLEQ proof has wrong length ({len(dleq_proof)} bytes, expected 64)",
|
||||
)
|
||||
|
||||
for i, input_map in enumerate(psbt.i):
|
||||
input_dleq_proofs = input_map.get_all_by_type(PSBT_IN_SP_DLEQ)
|
||||
for _, dleq_proof in input_dleq_proofs:
|
||||
if len(dleq_proof) != 64:
|
||||
return (
|
||||
False,
|
||||
f"Input {i} DLEQ proof has wrong length ({len(dleq_proof)} bytes, expected 64)",
|
||||
)
|
||||
|
||||
# Check TX_MODIFIABLE flag when PSBT_OUT_SCRIPT is set
|
||||
for output_map in psbt.o:
|
||||
if PSBT_OUT_SP_V0_INFO in output_map and PSBT_OUT_SCRIPT in output_map:
|
||||
if len(output_map.get(PSBT_OUT_SCRIPT, b"")) > 0:
|
||||
if psbt.g.get(PSBT_GLOBAL_TX_MODIFIABLE) != b"\x00":
|
||||
return (
|
||||
False,
|
||||
"PSBT_OUT_SCRIPT set for silent payments output but PSBT_GLOBAL_TX_MODIFIABLE not zeroed",
|
||||
)
|
||||
return True, None
|
||||
|
||||
|
||||
def validate_ecdh_coverage(psbt: PSBT) -> Tuple[bool, str]:
|
||||
return False, "ECDH coverage check not implemented yet"
|
||||
|
||||
|
||||
def validate_input_eligibility(psbt: PSBT) -> Tuple[bool, str]:
|
||||
return False, "Input eligibility check not implemented yet"
|
||||
|
||||
|
||||
def validate_output_scripts(psbt: PSBT) -> Tuple[bool, str]:
|
||||
return False, "Output scripts check not implemented yet"
|
||||
Reference in New Issue
Block a user