122 | + # more than 2 children sequentially would not produce a valid
123 | + # P2MR merkle root. It would also break here on a type mismatch.
124 | + # `tapbranch_hash` returns bytes, not the hex str this loop expects.
125 | + assert len(tree) == 2, f"expected binary branch, got {len(tree)} children"
126 | + left, right = compute_merkle_root(tree[0]), compute_merkle_root(tree[1])
127 | + return tapbranch_hash(left, right).hex()
Some of these functions accept or return hex-strings, while others use bytes for the same conceptual objects. Consistency is better: Either use hex strings for everything, or use bytes for everything. Input from test vectors (or output from the code) can be converted as needed in the tests.
Took a stab at this in 94b0f9d. It also cleaned up the code quite a bit in places.