Validate Electrum headers and corroborate the draw's block (B-28)
A single hostile Electrum server, or a MITM on the one active connection, could fabricate the block header the draw's entropy comes from and so pick the winner of every round: headers were accepted with no proof-of-work check and no link to the previous tip. app/rounds/draw.py adds header_meets_its_own_target (rejects a header whose hash doesn't satisfy the difficulty target it claims) and header_prev_hash. electrum/listener.py's _apply_header now rejects a header failing either check by raising HeaderValidationError, which ends the session the same way a dropped connection would so the listener rotates to the next configured server. ElectrumListener gains corroborate_header: before the draw uses a block, it's independently checked against the other configured servers and needs a majority to agree. rounds/scheduler.py's _wait_for_next_block now calls this and, on failure, logs why and waits for a further block instead of ever using an uncorroborated header. Certificate/hostname verification stays disabled, so this doesn't cover an attacker able to MITM every configured server at once - BUGS.md notes that as not covered. Suite grows from 151 to 165 tests. BUGS.md moves B-28 to Previously fixed.
This commit is contained in:
1 parent
7224ca0e66
commit
0ce0562fd7
7 files changed
+463
-63
No files matched your search
+41
-10
@@ -1,19 +1,50 @@
|
||||
import pytest
|
||||
|
||||
from app.rounds.draw import draw_winner, header_hex_to_block_hash
|
||||
from app.rounds.draw import (
|
||||
draw_winner,
|
||||
header_hex_to_block_hash,
|
||||
header_meets_its_own_target,
|
||||
header_prev_hash,
|
||||
)
|
||||
|
||||
# Real PLM mainnet block 477486: header from blockchain.block.header, hash
|
||||
# cross-checked against the blockhash reported by blockchain.transaction.get for a
|
||||
# tx confirmed in that block.
|
||||
_REAL_HEADER_HEX = (
|
||||
"0020ed30fbc39ee45200d10214f5107c5f36e7bf753032fd1d3279810c170000000000"
|
||||
"009a4ea723f732be4c538f3a2490837dd6560103d73ece606aa7d578a66700447b28875"
|
||||
"e6a47a61b1ad8012582"
|
||||
)
|
||||
|
||||
|
||||
def test_header_hex_to_block_hash_matches_known_mainnet_block():
|
||||
# Real PLM mainnet block 477486: header from blockchain.block.header, hash
|
||||
# cross-checked against the blockhash reported by blockchain.transaction.get
|
||||
# for a tx confirmed in that block.
|
||||
header_hex = (
|
||||
"0020ed30fbc39ee45200d10214f5107c5f36e7bf753032fd1d3279810c170000000000"
|
||||
"009a4ea723f732be4c538f3a2490837dd6560103d73ece606aa7d578a66700447b28875"
|
||||
"e6a47a61b1ad8012582"
|
||||
)
|
||||
known_block_hash = "00000000000008788b55ade13b74d54ceffda9e54315b802411be1ca65064e86"
|
||||
assert header_hex_to_block_hash(header_hex) == known_block_hash
|
||||
assert header_hex_to_block_hash(_REAL_HEADER_HEX) == known_block_hash
|
||||
|
||||
|
||||
def test_header_meets_its_own_target_accepts_a_real_mined_header():
|
||||
"""B-28: a genuinely mined mainnet header must pass its own self-consistency
|
||||
check — this isn't just a synthetic-header property."""
|
||||
assert header_meets_its_own_target(_REAL_HEADER_HEX) is True
|
||||
|
||||
|
||||
def test_header_meets_its_own_target_rejects_a_tampered_header():
|
||||
"""Flipping a single nonce bit changes the hash completely (avalanche effect)
|
||||
without changing the claimed difficulty, so a tampered-but-otherwise-real
|
||||
header should almost certainly fail — this is what would catch a
|
||||
hostile/MITM'd server replaying a real header with a doctored field."""
|
||||
tampered = bytearray(bytes.fromhex(_REAL_HEADER_HEX))
|
||||
tampered[-1] ^= 0xFF # flip the last byte of the nonce
|
||||
assert header_meets_its_own_target(tampered.hex()) is False
|
||||
|
||||
|
||||
def test_header_meets_its_own_target_rejects_wrong_length():
|
||||
assert header_meets_its_own_target("aa" * 10) is False
|
||||
|
||||
|
||||
def test_header_prev_hash_matches_the_known_previous_block():
|
||||
# Block 477486's predecessor, 477485 — independently known from the same chain.
|
||||
assert header_prev_hash(_REAL_HEADER_HEX) == "000000000000170c8179321dfd323075bfe7365f7c10f51402d10052e49ec3fb"
|
||||
|
||||
|
||||
def test_draw_winner_is_deterministic_and_within_range():
|
||||
|
||||
Reference in new issue
Block a user