mirror of
https://github.com/bitcoin/bips.git
synced 2026-07-27 18:13:38 +00:00
bip360: consistency changes to p2mr core functions
Standardizes all P2MR-specific functions to use bytes uniformly for input/output. Hex conversions are now confined to two boundaries: reading `script` field out of ScriptTree input, and comparing against hex-encoded test vector data in `run_single_test`. bech32 functions and s2w are left unchanged.
This commit is contained in:
@@ -75,35 +75,33 @@ def serialize_varbytes(b: bytes) -> bytes:
|
|||||||
#
|
#
|
||||||
# P2MR-specific Functions
|
# P2MR-specific Functions
|
||||||
#
|
#
|
||||||
def tapleaf_hash(script: str, tapleaf_ver: str = "c0") -> str:
|
def tapleaf_hash(script: bytes, tapleaf_ver: int = 0xc0) -> bytes:
|
||||||
"""Hash function for tree leaves"""
|
"""Hash function for tree leaves"""
|
||||||
if not script:
|
if not script:
|
||||||
raise ValueError("tapleaf_hash: script is required")
|
raise ValueError("tapleaf_hash: script is required")
|
||||||
leaf = h2b(tapleaf_ver) + serialize_varbytes(h2b(script))
|
leaf = bytes([tapleaf_ver]) + serialize_varbytes(script)
|
||||||
return tagged_hash("TapLeaf", leaf).hex()
|
return tagged_hash("TapLeaf", leaf)
|
||||||
|
|
||||||
|
|
||||||
def tapbranch_hash(left: str, right: str) -> bytes:
|
def tapbranch_hash(left: bytes, right: bytes) -> bytes:
|
||||||
"""Hash function for tree branches"""
|
"""Hash function for tree branches"""
|
||||||
return tagged_hash("TapBranch", b"".join(sorted((h2b(left), h2b(right)))))
|
return tagged_hash("TapBranch", b"".join(sorted((left, right))))
|
||||||
|
|
||||||
|
|
||||||
def compute_merkle_root(tree: ScriptTree) -> str:
|
def compute_merkle_root(tree: ScriptTree) -> bytes:
|
||||||
"""Recursively compute script tree merkle root"""
|
"""Recursively compute script tree merkle root"""
|
||||||
if isinstance(tree, dict): # Leaf
|
if isinstance(tree, dict): # Leaf
|
||||||
version = f"{tree['leafVersion']:x}"
|
version = tree["leafVersion"]
|
||||||
script = tree["script"]
|
script = h2b(tree["script"])
|
||||||
return tapleaf_hash(script=script, tapleaf_ver=version)
|
return tapleaf_hash(script=script, tapleaf_ver=version)
|
||||||
|
|
||||||
elif isinstance(tree, list): # Branch
|
elif isinstance(tree, list): # Branch
|
||||||
# Script trees are treated as strictly binary trees; each branch node should have
|
# 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
|
# exactly 2 children. This isn't a general n-ary fold, and combining
|
||||||
# more than 2 children sequentially would not produce a valid
|
# more than 2 children sequentially would not produce a valid P2MR merkle root.
|
||||||
# P2MR merkle root. It would also break here on a type mismatch.
|
|
||||||
# `tapbranch_hash` returns bytes, not the hex str this loop expects.
|
|
||||||
assert len(tree) == 2, f"expected binary branch, got {len(tree)} children"
|
assert len(tree) == 2, f"expected binary branch, got {len(tree)} children"
|
||||||
left, right = compute_merkle_root(tree[0]), compute_merkle_root(tree[1])
|
left, right = compute_merkle_root(tree[0]), compute_merkle_root(tree[1])
|
||||||
return tapbranch_hash(left, right).hex()
|
return tapbranch_hash(left, right)
|
||||||
|
|
||||||
else: # badbadnotgood
|
else: # badbadnotgood
|
||||||
raise ValueError("Invalid tree node")
|
raise ValueError("Invalid tree node")
|
||||||
@@ -128,7 +126,7 @@ def compute_control_block(path: int, tree: ScriptTree) -> bytes:
|
|||||||
assert len(tree) == 2
|
assert len(tree) == 2
|
||||||
sibling = tree[(path & 1) ^ 1]
|
sibling = tree[(path & 1) ^ 1]
|
||||||
tree = tree[(path & 1)]
|
tree = tree[(path & 1)]
|
||||||
control_block = h2b(compute_merkle_root(sibling)) + control_block
|
control_block = compute_merkle_root(sibling) + control_block
|
||||||
path >>= 1
|
path >>= 1
|
||||||
|
|
||||||
assert isinstance(tree, dict)
|
assert isinstance(tree, dict)
|
||||||
@@ -263,15 +261,15 @@ def encode(hrp, witver, witprog):
|
|||||||
#
|
#
|
||||||
# BIP-360 Test Code
|
# BIP-360 Test Code
|
||||||
#
|
#
|
||||||
def collect_leaf_hashes(tree: ScriptTree) -> List[str]:
|
def collect_leaf_hashes(tree: ScriptTree) -> List[bytes]:
|
||||||
"""Recursively collect leaf hashes in order (for verification)"""
|
"""Recursively collect leaf hashes in order (for verification)"""
|
||||||
if isinstance(tree, dict): # Leaf
|
if isinstance(tree, dict): # Leaf
|
||||||
version = f"{tree['leafVersion']:x}"
|
version = tree["leafVersion"]
|
||||||
script = tree["script"]
|
script = h2b(tree["script"])
|
||||||
return [tapleaf_hash(script=script, tapleaf_ver=version)]
|
return [tapleaf_hash(script=script, tapleaf_ver=version)]
|
||||||
|
|
||||||
elif isinstance(tree, list): # Branch: recurse on children
|
elif isinstance(tree, list): # Branch: recurse on children
|
||||||
hashes: List[str] = []
|
hashes: List[bytes] = []
|
||||||
for sub in tree:
|
for sub in tree:
|
||||||
hashes.extend(collect_leaf_hashes(sub))
|
hashes.extend(collect_leaf_hashes(sub))
|
||||||
return hashes
|
return hashes
|
||||||
@@ -295,13 +293,13 @@ def walk_script_tree_paths(
|
|||||||
return lchild_paths + rchild_paths
|
return lchild_paths + rchild_paths
|
||||||
|
|
||||||
|
|
||||||
def collect_control_blocks(script_tree: ScriptTree) -> List[str]:
|
def collect_control_blocks(script_tree: ScriptTree) -> List[bytes]:
|
||||||
"""Return control blocks for all leaves in tree declaration order.
|
"""Return control blocks for all leaves in tree declaration order.
|
||||||
Note: This ordering is for testing purposes. In practice, you would
|
Note: This ordering is for testing purposes. In practice, you would
|
||||||
compute the control block for a specific leaf at spend-time using
|
compute the control block for a specific leaf at spend-time using
|
||||||
`compute_control_block(path, tree)`."""
|
`compute_control_block(path, tree)`."""
|
||||||
leaf_node_paths: List[int] = walk_script_tree_paths(script_tree)
|
leaf_node_paths: List[int] = walk_script_tree_paths(script_tree)
|
||||||
return [compute_control_block(path, script_tree).hex() for path in leaf_node_paths]
|
return [compute_control_block(path, script_tree) for path in leaf_node_paths]
|
||||||
|
|
||||||
|
|
||||||
def extract_test_data(v: Dict[str, Any]) -> Dict[str, Any]:
|
def extract_test_data(v: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
@@ -353,7 +351,9 @@ def run_single_test(v: Dict[str, Any], test_num: int) -> bool:
|
|||||||
# General Case: Single- and Multi-Leaf script trees
|
# General Case: Single- and Multi-Leaf script trees
|
||||||
else:
|
else:
|
||||||
# test script leaf hashing
|
# test script leaf hashing
|
||||||
derived_leaf_hashes = collect_leaf_hashes(v["script_tree"])
|
derived_leaf_hashes = [
|
||||||
|
h.hex() for h in collect_leaf_hashes(v["script_tree"])
|
||||||
|
]
|
||||||
assert derived_leaf_hashes == v["leaf_hashes"], (
|
assert derived_leaf_hashes == v["leaf_hashes"], (
|
||||||
f"leaf hash mismatch:\n"
|
f"leaf hash mismatch:\n"
|
||||||
f" derived: {derived_leaf_hashes}\n"
|
f" derived: {derived_leaf_hashes}\n"
|
||||||
@@ -362,7 +362,7 @@ def run_single_test(v: Dict[str, Any], test_num: int) -> bool:
|
|||||||
print("Leaf Hashes: [\n" + ",\n".join(derived_leaf_hashes) + "\n]")
|
print("Leaf Hashes: [\n" + ",\n".join(derived_leaf_hashes) + "\n]")
|
||||||
|
|
||||||
# test merkle root computation
|
# test merkle root computation
|
||||||
derived_merkle_root = compute_merkle_root(v["script_tree"])
|
derived_merkle_root = compute_merkle_root(v["script_tree"]).hex()
|
||||||
assert derived_merkle_root == v["merkle_root"], (
|
assert derived_merkle_root == v["merkle_root"], (
|
||||||
f"merkle root mismatch: "
|
f"merkle root mismatch: "
|
||||||
f"derived={derived_merkle_root}, expected={v['merkle_root']}"
|
f"derived={derived_merkle_root}, expected={v['merkle_root']}"
|
||||||
@@ -390,7 +390,9 @@ def run_single_test(v: Dict[str, Any], test_num: int) -> bool:
|
|||||||
|
|
||||||
# test control block derivation
|
# test control block derivation
|
||||||
if v["script_path_control_blocks"]:
|
if v["script_path_control_blocks"]:
|
||||||
derived_control_blocks = collect_control_blocks(v["script_tree"])
|
derived_control_blocks = [
|
||||||
|
cb.hex() for cb in collect_control_blocks(v["script_tree"])
|
||||||
|
]
|
||||||
assert derived_control_blocks == v["script_path_control_blocks"], (
|
assert derived_control_blocks == v["script_path_control_blocks"], (
|
||||||
f"control blocks mismatch:\n"
|
f"control blocks mismatch:\n"
|
||||||
f" derived: {derived_control_blocks}\n"
|
f" derived: {derived_control_blocks}\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user