1
0
mirror of https://github.com/bitcoin/bips.git synced 2026-09-21 19:04:28 +00:00

Merge pull request #2273 from let-the-dreamers-rise/bip360-refimpl-depth-bound-and-raises

bip360: bound merkle depth at 128 and replace validation asserts in ref-impl
This commit is contained in:
Jon Atack
2026-09-20 20:14:22 -07:00
committed by GitHub

View File

@@ -23,6 +23,11 @@ BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32M_CONST = 0x2BC830A3
MAX_COMPACT_SIZE = 2**64 - 1
# BIP 360, Script Validation: a control block "must have length 1 + 32 * m, for a
# value of m that is an integer between 0 and 128, inclusive" -- so no script leaf
# may sit deeper than 128 branches, or its control block is consensus-invalid.
CONTROL_MAX_NODE_COUNT = 128
# A script tree node is either a leaf (dict) or a branch (list of nodes)
ScriptTree = Union[Dict[str, Any], List["ScriptTree"]]
@@ -88,7 +93,7 @@ def tapbranch_hash(left: bytes, right: bytes) -> bytes:
return tagged_hash("TapBranch", b"".join(sorted((left, right))))
def compute_merkle_root(tree: ScriptTree) -> bytes:
def compute_merkle_root(tree: ScriptTree, depth: int = 0) -> bytes:
"""Recursively compute script tree merkle root"""
if isinstance(tree, dict): # Leaf
version = tree["leafVersion"]
@@ -99,8 +104,15 @@ def compute_merkle_root(tree: ScriptTree) -> bytes:
# Script trees are treated strictly as binary trees; each branch node should have
# exactly 2 children. This isn't a general n-ary fold, and combining
# more than 2 children sequentially would not produce a valid P2MR merkle root.
assert len(tree) == 2, f"expected binary branch, got {len(tree)} children"
left, right = compute_merkle_root(tree[0]), compute_merkle_root(tree[1])
if depth >= CONTROL_MAX_NODE_COUNT:
raise ValueError(
f"compute_merkle_root: leaf deeper than {CONTROL_MAX_NODE_COUNT}; "
"its control block would exceed the consensus length limit"
)
if len(tree) != 2:
raise ValueError(f"expected binary branch, got {len(tree)} children")
left = compute_merkle_root(tree[0], depth + 1)
right = compute_merkle_root(tree[1], depth + 1)
return tapbranch_hash(left, right)
else: # badbadnotgood
@@ -118,18 +130,26 @@ def compute_control_block(path: int, tree: ScriptTree) -> bytes:
"""
if isinstance(tree, dict):
return bytes([tree["leafVersion"] | 1])
assert isinstance(tree, list) and len(tree) == 2
if not (isinstance(tree, list) and len(tree) == 2):
raise ValueError("compute_control_block: invalid tree node")
control_block = b""
while isinstance(tree, list):
assert len(tree) == 2
if len(tree) != 2:
raise ValueError(f"expected binary branch, got {len(tree)} children")
sibling = tree[(path & 1) ^ 1]
tree = tree[(path & 1)]
control_block = compute_merkle_root(sibling) + control_block
path >>= 1
assert isinstance(tree, dict)
if not isinstance(tree, dict):
raise ValueError("compute_control_block: path does not terminate at a leaf")
if len(control_block) > 32 * CONTROL_MAX_NODE_COUNT:
raise ValueError(
f"compute_control_block: {1 + len(control_block)} bytes exceeds the "
f"consensus limit of 1 + 32 * {CONTROL_MAX_NODE_COUNT}"
)
return bytes([tree["leafVersion"] | 1]) + control_block
@@ -412,6 +432,39 @@ def run_single_test(v: Dict[str, Any], test_num: int) -> bool:
return False
def negative_structure_tests() -> None:
"""Malformed trees must raise -- never silently return a wrong root -- including under python -O."""
print("\nRunning negative structure tests...")
leaf_a = {"leafVersion": 0xC0, "script": "51"}
leaf_b = {"leafVersion": 0xC0, "script": "52"}
leaf_c = {"leafVersion": 0xC0, "script": "53"}
# A ternary branch must be refused, never reduced to its first two children.
try:
compute_merkle_root([leaf_a, leaf_b, leaf_c])
raise SystemExit("FAILED: ternary branch accepted")
except ValueError:
pass
# A leaf may sit at depth 128 (m = 128) but never deeper.
tree: ScriptTree = leaf_a
for _ in range(CONTROL_MAX_NODE_COUNT):
tree = [tree, leaf_b]
cb = compute_control_block(0, tree) # depth 128: still valid
if len(cb) != 1 + 32 * CONTROL_MAX_NODE_COUNT:
raise SystemExit("FAILED: wrong control block length at maximum depth")
tree = [tree, leaf_b] # depth 129: consensus-invalid
for attempt in (lambda: compute_merkle_root(tree), lambda: compute_control_block(0, tree)):
try:
attempt()
raise SystemExit("FAILED: >128-deep tree accepted")
except ValueError:
pass
print("3/3 negative structure tests passed.")
def BIP360_tests() -> None:
"""Run all BIP-360 Test Vectors."""
print("\nRunning BIP-0360 Pay-to-Merkle-Root (P2MR) Tests...")
@@ -421,6 +474,7 @@ def BIP360_tests() -> None:
passed = sum(run_single_test(v, i + 1) for i, v in enumerate(test_vectors))
print(f"\n{passed}/{len(test_vectors)} BIP-360 tests passed successfully.")
negative_structure_tests()
if __name__ == "__main__":