montana/Монтана-Протокол/Код/scripts/oracle_python_sha256.py

137 lines
5.4 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
External SHA-256 oracle для composite hash compositions Montana M4.
Назначение: Pass 25 (Independent Oracle / Differential Check) per CRITIC.md.
Текущие unit tests cross-check `domain::X` constants против литералов
`b"mt-X"` через SAME Rust SHA-256 implementation — circular validation.
Этот script даёт computation от Python `hashlib.sha256` (CPython OpenSSL
binding) — независимая reference для cross-impl conformance.
Запуск:
python3 scripts/oracle_python_sha256.py
Вывод: hex digests для каждого M4 composite hash на binding test inputs.
Tests в crates/mt-lottery/tests/external_oracle.rs + аналог mt-entry
читают этот вывод и assertion (либо hardcoded test fixture обновлён).
Все формулы соответствуют spec через mt-codec::domain registry +
NUL byte separator pattern (mt-crypto::hash):
SHA-256(domain || 0x00 || part0 || part1 || ...)
"""
import hashlib
import sys
def hash_with_domain(domain: bytes, *parts: bytes) -> bytes:
"""Эквивалент mt-crypto::hash(domain, &[parts]):
SHA-256(domain || 0x00 || parts[0] || parts[1] || ...).
"""
h = hashlib.sha256()
h.update(domain)
h.update(b"\x00")
for p in parts:
h.update(p)
return h.digest()
def fixed_block(byte_value: int, length: int) -> bytes:
"""Helper — fixed byte repeated. Matches тесты Rust (е.g., [0x11; 32])."""
return bytes([byte_value]) * length
def run() -> dict:
"""Compute oracle outputs для всех M4 composite hashes на binding inputs.
Inputs соответствуют mt-lottery / mt-entry / mt-consensus determinism
invariants test vectors — каждый test использует те же byte patterns,
Python oracle output должен match Rust output байт-в-байт.
"""
# Common test inputs (matching crates/mt-lottery/tests/determinism_invariants.rs
# и crates/mt-entry/tests/determinism_invariants.rs)
t_r = fixed_block(0x11, 32)
cba = fixed_block(0x22, 32)
node_id = fixed_block(0x33, 32)
pubkey = fixed_block(0x33, 1952) # PUBLIC_KEY_SIZE
results = {}
# --- mt-lottery::compute_endpoint ---
# spec: SHA-256("mt-lottery" || NUL || t_r || cba || node_id || w_le)
# window_index = 7, u32 LE = 07 00 00 00
w_le = (7).to_bytes(4, "little")
results["compute_endpoint(t_r=11..,cba=22..,node_id=33..,w=7)"] = (
hash_with_domain(b"mt-lottery", t_r, cba, node_id, w_le).hex()
)
# --- mt-entry::candidate_vdf_init ---
# spec: SHA-256("mt-candidate-vdf-init" || NUL || timechain || cba || node_id)
results["candidate_vdf_init(t_r=11..,cba=22..,node_id=33..)"] = (
hash_with_domain(b"mt-candidate-vdf-init", t_r, cba, node_id).hex()
)
# --- mt-entry::selection_sort_key ---
# spec: SHA-256("mt-selection" || NUL || timechain || cba || node_id)
results["selection_sort_key(t_r=11..,cba=22..,node_id=33..)"] = (
hash_with_domain(b"mt-selection", t_r, cba, node_id).hex()
)
# --- mt-entry::nr_sort_key ---
# spec: SHA-256("mt-nodereg-sort" || NUL || timechain || cba || node_pubkey)
# node_pubkey = [0x33; 1952] (PUBLIC_KEY_SIZE)
results["nr_sort_key(t_r=11..,cba=22..,pubkey=33..*1952)"] = (
hash_with_domain(b"mt-nodereg-sort", t_r, cba, pubkey).hex()
)
# --- Distinct domain separators check ---
# Three different domains для same (t_r, cba, node_id) input — must produce
# 3 different outputs (NUL pattern + domain isolation).
sel = hash_with_domain(b"mt-selection", t_r, cba, node_id)
vdf = hash_with_domain(b"mt-candidate-vdf-init", t_r, cba, node_id)
nr = hash_with_domain(b"mt-nodereg-sort", t_r, cba, pubkey)
distinct = (sel != vdf) and (vdf != nr) and (sel != nr)
results["distinct_domains_for_three_compositions"] = "PASS" if distinct else "FAIL"
# --- compute_endpoint dependencies (each input change → different output) ---
base = hash_with_domain(b"mt-lottery", t_r, cba, node_id, w_le)
diff_t_r = hash_with_domain(
b"mt-lottery", fixed_block(0xFF, 32), cba, node_id, w_le
)
diff_cba = hash_with_domain(
b"mt-lottery", t_r, fixed_block(0xFF, 32), node_id, w_le
)
diff_node = hash_with_domain(
b"mt-lottery", t_r, cba, fixed_block(0xFF, 32), w_le
)
diff_w = hash_with_domain(b"mt-lottery", t_r, cba, node_id, (8).to_bytes(4, "little"))
sensitivity = (
base != diff_t_r and base != diff_cba and base != diff_node and base != diff_w
)
results["compute_endpoint_input_sensitivity"] = (
"PASS" if sensitivity else "FAIL"
)
return results
def main():
results = run()
print("# External SHA-256 oracle (Python hashlib) для M4 composite hashes")
print("# Pass 25 Independent Oracle: cross-check Rust mt-crypto::hash output")
print()
for key, value in results.items():
print(f"{key:60s} = {value}")
# Exit non-zero если distinct/sensitivity checks fail
if (
results.get("distinct_domains_for_three_compositions") != "PASS"
or results.get("compute_endpoint_input_sensitivity") != "PASS"
):
print("\n[FAIL] domain separation либо input sensitivity broken")
sys.exit(1)
print("\n[PASS] all oracle invariants hold")
if __name__ == "__main__":
main()