194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Replay the VM's BLAKE2s-style compression core for xfalcon."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import glob
|
|
import re
|
|
import struct
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
MASK32 = 0xFFFFFFFF
|
|
|
|
SIGMA = [
|
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
|
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
|
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
|
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
|
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
|
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
|
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
|
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
|
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
|
|
]
|
|
|
|
|
|
SAMPLE_V0 = [
|
|
0xA82057D6, 0xA9A24FF4, 0x9138D3FD, 0x2A2193F3,
|
|
0x2ADEF3F4, 0x9876EF16, 0x9ABED34F, 0x9103DE12,
|
|
0xA92157F6, 0xA9A24FF4, 0x9138D3FD, 0x2A2193F3,
|
|
0x2ADEF3D5, 0x9876EF16, 0x65412CB0, 0x9103DE12,
|
|
]
|
|
|
|
SAMPLE_M = [
|
|
0x0C5B0466, 0x04580152, 0x51500555, 0x00565451,
|
|
0x36753729, 0x5B72176D, 0x070A1F60, 0x7C662341,
|
|
0x682F4A37, 0x26562F2D, 0x47573177, 0x540A115A,
|
|
0x715D5A79, 0x061C581D, 0x6D0E0D1C, 0x0F230801,
|
|
]
|
|
|
|
SAMPLE_V_FINAL = [
|
|
0x296C7D05, 0xA2E09447, 0x62AB2823, 0xB3FD4E92,
|
|
0xF96DE833, 0xA5517BB8, 0xBEBB2C06, 0xFAD21B26,
|
|
0x2929349D, 0xF418B33C, 0x7325D323, 0xB446F83B,
|
|
0x04D647B0, 0x11FB17F9, 0x0CD935BC, 0x858F3D9F,
|
|
]
|
|
|
|
SAMPLE_DIGEST = [
|
|
0xA8651E4E, 0xFF5A688F, 0x80B628FD, 0x2D9A255A,
|
|
0xD7655C77, 0x2CDC8357, 0x28DCCAF5, 0xEE5EF8AB,
|
|
]
|
|
|
|
|
|
def ror32(value: int, count: int) -> int:
|
|
value &= MASK32
|
|
count &= 31
|
|
return ((value >> count) | ((value << (32 - count)) & MASK32)) & MASK32
|
|
|
|
|
|
def compress(v0: list[int], m: list[int]) -> list[int]:
|
|
v = [x & MASK32 for x in v0]
|
|
|
|
def g(a: int, b: int, c: int, d: int, x: int, y: int) -> None:
|
|
v[a] = (v[a] + v[b] + x) & MASK32
|
|
v[d] = ror32(v[d] ^ v[a], 16)
|
|
v[c] = (v[c] + v[d]) & MASK32
|
|
v[b] = ror32(v[b] ^ v[c], 12)
|
|
v[a] = (v[a] + v[b] + y) & MASK32
|
|
v[d] = ror32(v[d] ^ v[a], 8)
|
|
v[c] = (v[c] + v[d]) & MASK32
|
|
v[b] = ror32(v[b] ^ v[c], 7)
|
|
|
|
for s in SIGMA:
|
|
g(0, 4, 8, 12, m[s[0]], m[s[1]])
|
|
g(1, 5, 9, 13, m[s[2]], m[s[3]])
|
|
g(2, 6, 10, 14, m[s[4]], m[s[5]])
|
|
g(3, 7, 11, 15, m[s[6]], m[s[7]])
|
|
g(0, 5, 10, 15, m[s[8]], m[s[9]])
|
|
g(1, 6, 11, 12, m[s[10]], m[s[11]])
|
|
g(2, 7, 8, 13, m[s[12]], m[s[13]])
|
|
g(3, 4, 9, 14, m[s[14]], m[s[15]])
|
|
|
|
return v
|
|
|
|
|
|
def finalize_digest(v0: list[int], v_final: list[int]) -> list[int]:
|
|
return [(v0[i] ^ v_final[i] ^ v_final[i + 8]) & MASK32 for i in range(8)]
|
|
|
|
|
|
def words_hex(words: list[int]) -> str:
|
|
return " ".join(f"{x & MASK32:08x}" for x in words)
|
|
|
|
|
|
def digest_hex(words: list[int]) -> str:
|
|
return "".join((x & MASK32).to_bytes(4, "big").hex() for x in words)
|
|
|
|
|
|
def newest_log() -> Path:
|
|
logs = sorted(glob.glob("out/xfalcon_blake_params_*.log"))
|
|
if not logs:
|
|
raise SystemExit("no xfalcon_blake_params log found")
|
|
return Path(logs[-1])
|
|
|
|
|
|
def collect_dump(text: str, name: str, base_rel: int) -> bytearray:
|
|
buf = bytearray()
|
|
pat = rf"\[BLK\]\[dump\.{re.escape(name)}\] rel=0x([0-9a-f]+).*? hex=([0-9a-f]+)"
|
|
for m in re.finditer(pat, text):
|
|
rel = int(m.group(1), 16)
|
|
off = rel - base_rel
|
|
data = bytes.fromhex(m.group(2))
|
|
if off < 0:
|
|
continue
|
|
if len(buf) < off + len(data):
|
|
buf.extend(b"\x00" * (off + len(data) - len(buf)))
|
|
buf[off : off + len(data)] = data
|
|
return buf
|
|
|
|
|
|
def qwords_low32(buf: bytes, off: int, count: int) -> list[int]:
|
|
return [struct.unpack_from("<Q", buf, off + i * 8)[0] & MASK32 for i in range(count)]
|
|
|
|
|
|
def parse_log(path: Path) -> tuple[list[int], list[int], list[int], list[int]]:
|
|
text = path.read_text(encoding="utf-8", errors="replace")
|
|
|
|
work = collect_dump(text, "work_2e00_at_compress", 0x2E00)
|
|
if len(work) < 0x228:
|
|
raise SystemExit("log missing work_2e00_at_compress dump")
|
|
|
|
v0 = qwords_low32(work, 0x2E28 - 0x2E00, 16)
|
|
m_words = qwords_low32(work, 0x2FA8 - 0x2E00, 16)
|
|
|
|
table: dict[int, int] = {}
|
|
for sm in re.finditer(r"\[BLK\]\[store\].*?rel=0x([0-9a-f]+).*?val=0x([0-9a-f]+)", text):
|
|
rel = int(sm.group(1), 16)
|
|
if 0x2E28 <= rel < 0x2EA8 and (rel - 0x2E28) % 8 == 0:
|
|
table[(rel - 0x2E28) // 8] = int(sm.group(2), 16) & MASK32
|
|
if len(table) < 16:
|
|
raise SystemExit(f"log missing final table stores: got {len(table)}")
|
|
v_final = [table[i] for i in range(16)]
|
|
|
|
digest: dict[int, int] = {}
|
|
for sm in re.finditer(r"\[BLK\]\[store\].*?rel=0x([0-9a-f]+).*?val=0x([0-9a-f]+)", text):
|
|
rel = int(sm.group(1), 16)
|
|
if 0x103A68 <= rel < 0x103AA8 and (rel - 0x103A68) % 8 == 0:
|
|
digest[(rel - 0x103A68) // 8] = int(sm.group(2), 16) & MASK32
|
|
digest_words = [digest[i] for i in range(8)] if len(digest) >= 8 else []
|
|
return v0, m_words, v_final, digest_words
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) > 1:
|
|
path = Path(sys.argv[1])
|
|
else:
|
|
path = newest_log()
|
|
|
|
if path.exists():
|
|
v0, m_words, expected_v, expected_digest = parse_log(path)
|
|
print(f"log={path}")
|
|
else:
|
|
v0, m_words, expected_v, expected_digest = (
|
|
SAMPLE_V0,
|
|
SAMPLE_M,
|
|
SAMPLE_V_FINAL,
|
|
SAMPLE_DIGEST,
|
|
)
|
|
print("using built-in sample")
|
|
|
|
got_v = compress(v0, m_words)
|
|
got_digest = finalize_digest(v0, got_v)
|
|
|
|
print("v0: " + words_hex(v0))
|
|
print("m: " + words_hex(m_words))
|
|
print("v_calc: " + words_hex(got_v))
|
|
print("v_expect:" + words_hex(expected_v))
|
|
print("digest: " + words_hex(got_digest))
|
|
print("hex: " + digest_hex(got_digest))
|
|
|
|
ok_v = got_v == expected_v
|
|
ok_d = not expected_digest or got_digest == expected_digest
|
|
if ok_v and ok_d:
|
|
print("[OK] xfalcon blake core replay verified")
|
|
return 0
|
|
print(f"[FAIL] v_ok={ok_v} digest_ok={ok_d}")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|