mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-25 16:47:52 +02:00
147 lines
5.5 KiB
Python
147 lines
5.5 KiB
Python
"""Argon2id canonicalization — preimage construction and leading-zero check.
|
|
|
|
Source of truth: ``infonet-economy/RULES_SKELETON.md`` §3.10 step 0.5
|
|
+ the ``CONFIG['bootstrap_pow_argon2id_*']`` comment block.
|
|
|
|
Two consensus-critical pieces of canonicalization:
|
|
|
|
1. **Canonical preimage** — exact byte sequence the Argon2id call
|
|
takes as `password`. UTF-8 encoded, "|"-delimited, no trailing
|
|
delimiter. Format:
|
|
|
|
"bootstrap_resolution_vote" || protocol_version || node_id ||
|
|
market_id || side || snapshot_event_hash || pow_nonce
|
|
|
|
The component order MUST match the spec exactly. Any deviation
|
|
causes consensus fork.
|
|
|
|
2. **Leading-zero check** — operates on RAW Argon2id output bytes,
|
|
MSB first (big-endian bit numbering). Difficulty N requires the
|
|
first N bits of the 32-byte output to be zero. With difficulty=16
|
|
that means the first 2 bytes are 0x00 0x00.
|
|
|
|
Sprint 8 does NOT execute Argon2id itself — the verifier here takes
|
|
an already-computed hash bytes object as input. Production callers
|
|
wire this through ``privacy-core`` Rust binding. A stub Python
|
|
implementation is intentionally absent to avoid accidental drift
|
|
between the Sprint 8 pure-Python path and the eventual Rust path.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from services.infonet.config import CONFIG, IMMUTABLE_PRINCIPLES
|
|
|
|
|
|
def canonical_pow_preimage(
|
|
*,
|
|
node_id: str,
|
|
market_id: str,
|
|
side: str,
|
|
snapshot_event_hash: str,
|
|
pow_nonce: int,
|
|
protocol_version: str | None = None,
|
|
) -> bytes:
|
|
"""Build the canonical preimage for the Argon2id ``password`` input.
|
|
|
|
Returns UTF-8 bytes of ``"bootstrap_resolution_vote|<version>|<node>|
|
|
<market>|<side>|<snapshot_hash>|<nonce>"`` with NO trailing delimiter.
|
|
|
|
``protocol_version`` defaults to ``IMMUTABLE_PRINCIPLES['protocol_version']``
|
|
— it's pulled at call time so a hard-fork upgrade picks up the
|
|
new value automatically. Pass an explicit value when computing
|
|
against a hypothetical version (test scenarios).
|
|
"""
|
|
if not isinstance(node_id, str) or not node_id:
|
|
raise ValueError("node_id must be a non-empty string")
|
|
if not isinstance(market_id, str) or not market_id:
|
|
raise ValueError("market_id must be a non-empty string")
|
|
if side not in ("yes", "no"):
|
|
raise ValueError("side must be 'yes' or 'no'")
|
|
if not isinstance(snapshot_event_hash, str) or not snapshot_event_hash:
|
|
raise ValueError("snapshot_event_hash must be a non-empty string")
|
|
if not isinstance(pow_nonce, int) or isinstance(pow_nonce, bool) or pow_nonce < 0:
|
|
raise ValueError("pow_nonce must be a non-negative int")
|
|
pv = protocol_version if protocol_version is not None else IMMUTABLE_PRINCIPLES["protocol_version"]
|
|
if not isinstance(pv, str) or not pv:
|
|
raise ValueError("protocol_version must be a non-empty string")
|
|
|
|
parts = [
|
|
"bootstrap_resolution_vote",
|
|
pv,
|
|
node_id,
|
|
market_id,
|
|
side,
|
|
snapshot_event_hash,
|
|
str(pow_nonce),
|
|
]
|
|
return "|".join(parts).encode("utf-8")
|
|
|
|
|
|
def has_leading_zero_bits(raw_output: bytes, difficulty: int) -> bool:
|
|
"""``True`` if the first ``difficulty`` bits of ``raw_output``
|
|
are all zero.
|
|
|
|
Bit numbering: MSB first (big-endian). Byte order: as-is in the
|
|
raw output. With difficulty=16, the first two bytes must be
|
|
``\\x00\\x00``. With difficulty=4, the first byte must be in
|
|
``\\x00``..``\\x0f``.
|
|
"""
|
|
if not isinstance(raw_output, (bytes, bytearray)):
|
|
raise ValueError("raw_output must be bytes")
|
|
if not isinstance(difficulty, int) or difficulty < 0:
|
|
raise ValueError("difficulty must be a non-negative int")
|
|
if difficulty == 0:
|
|
return True
|
|
|
|
full_bytes, remaining_bits = divmod(difficulty, 8)
|
|
if len(raw_output) < full_bytes + (1 if remaining_bits else 0):
|
|
return False
|
|
for i in range(full_bytes):
|
|
if raw_output[i] != 0:
|
|
return False
|
|
if remaining_bits:
|
|
# The next byte's top `remaining_bits` bits must be zero.
|
|
next_byte = raw_output[full_bytes]
|
|
# Mask of the top `remaining_bits` bits (MSB first).
|
|
mask = ((0xFF << (8 - remaining_bits)) & 0xFF)
|
|
if (next_byte & mask) != 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
def verify_pow_structure(
|
|
*,
|
|
raw_output: bytes,
|
|
difficulty: int | None = None,
|
|
expected_output_len: int | None = None,
|
|
) -> bool:
|
|
"""Verify the Argon2id output's structural properties.
|
|
|
|
- Output length must match ``expected_output_len`` (default
|
|
``CONFIG['bootstrap_pow_argon2id_output_len']``, fixed at 32).
|
|
- Leading zero check passes for ``difficulty`` (default
|
|
``CONFIG['bootstrap_pow_difficulty']``).
|
|
|
|
Does NOT verify that ``raw_output`` was actually produced by
|
|
Argon2id from the canonical preimage — that's the caller's job
|
|
via ``privacy-core`` Rust binding (or Python's ``argon2-cffi`` in
|
|
test environments). Sprint 8 keeps the cryptographic-call layer
|
|
as an external concern.
|
|
"""
|
|
if not isinstance(raw_output, (bytes, bytearray)):
|
|
return False
|
|
expected = expected_output_len if expected_output_len is not None else int(
|
|
CONFIG["bootstrap_pow_argon2id_output_len"]
|
|
)
|
|
if len(raw_output) != expected:
|
|
return False
|
|
diff = difficulty if difficulty is not None else int(CONFIG["bootstrap_pow_difficulty"])
|
|
return has_leading_zero_bits(raw_output, diff)
|
|
|
|
|
|
__all__ = [
|
|
"canonical_pow_preimage",
|
|
"has_leading_zero_bits",
|
|
"verify_pow_structure",
|
|
]
|