137 lines
5.4 KiB
Python
137 lines
5.4 KiB
Python
|
|
#!/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()
|