"""Merkle proofs (B-83, TX-22, ELC-26). `blockchain.transaction.get_merkle` answers with the branch that links a transaction to its block's merkle root. Checking it against a header **we hold** is what turns "a server says this tx is at height h" into something a hostile server cannot fake without mining. """ import hashlib _HEADER_LENGTH_BYTES = 80 _MERKLE_ROOT_OFFSET = 36 _MERKLE_ROOT_LENGTH = 32 _MAX_BRANCH_LENGTH = 40 # 2^40 transactions in a block is already absurd def _sha256d(data: bytes) -> bytes: return hashlib.sha256(hashlib.sha256(data).digest()).digest() def header_merkle_root(header_hex: str) -> bytes: """The merkle root committed to by a raw 80-byte header, in internal byte order.""" header = bytes.fromhex(header_hex) if len(header) != _HEADER_LENGTH_BYTES: raise ValueError(f"a block header is {_HEADER_LENGTH_BYTES} bytes, got {len(header)}") return header[_MERKLE_ROOT_OFFSET : _MERKLE_ROOT_OFFSET + _MERKLE_ROOT_LENGTH] def merkle_root_from_branch(txid: str, branch: list[str], position: int) -> bytes: """The merkle root (internal byte order) a transaction `txid` at index `position` of its block reaches through `branch` — the sibling hashes, bottom-up, in the display order Electrum uses for txids.""" if position < 0 or len(branch) > _MAX_BRANCH_LENGTH or position >> len(branch): raise ValueError("merkle position does not fit the branch") current = bytes.fromhex(txid)[::-1] index = position for sibling_hex in branch: sibling = bytes.fromhex(sibling_hex)[::-1] if len(sibling) != 32: raise ValueError("merkle branch element is not 32 bytes") current = _sha256d(sibling + current) if index & 1 else _sha256d(current + sibling) index >>= 1 return current def proof_matches_header(txid: str, height: int, proof: object, header_hex: str) -> bool: """Does `proof` — a `get_merkle` reply — put `txid` in the block at `height` whose header is `header_hex`? Any malformed reply is simply "no".""" try: if not isinstance(proof, dict) or int(proof["block_height"]) != height: return False branch = proof["merkle"] if not isinstance(branch, list): return False root = merkle_root_from_branch(txid, [str(item) for item in branch], int(proof["pos"])) return root == header_merkle_root(header_hex) except (KeyError, TypeError, ValueError): return False