Files
plm-lottery/app/electrum/merkle.py
T

57 lines
2.4 KiB
Python
Raw Normal View History

"""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