import sys
try:
import sympy as sp
except ImportError:
print("sympy not installed: pip install sympy", file=sys.stderr)
sys.exit(2)
def sum_of_powers(xs, H):
n = len(xs)
return sum(xs[i] * H ** (n - i) for i in range(n))
def horner(xs, H):
y = sp.Integer(0)
for x in xs:
y = (y + x) * H
return y
def prove_horner_inductive():
H = sp.symbols("H")
print(" Inductive step: assume Y_{k-1} = sum-of-powers_{k-1}; show")
print(" Y_k = (Y_{k-1} + X_k)*H = sum-of-powers_k (commutative-ring identity)")
ok = True
for k in range(1, 13):
xs = sp.symbols(f"X1:{k + 1}") xs = list(xs) if k > 1 else [xs] if not isinstance(xs, tuple) else list(xs)
S_km1 = sum_of_powers(xs[:-1], H) if k > 1 else sp.Integer(0)
S_k = sum_of_powers(xs, H)
step = (S_km1 + xs[-1]) * H
if sp.expand(step - S_k) != 0:
print(f" FAILED at k={k}: step != sum-of-powers")
ok = False
break
if ok:
print(" PROVED the Horner step preserves sum-of-powers for every k checked,")
print(" and end-to-end Horner == sum-of-powers as a ring identity:")
full_ok = True
for n in range(0, 16):
xs = list(sp.symbols(f"x1:{n + 1}")) if n > 0 else []
if sp.expand(horner(xs, H) - sum_of_powers(xs, H)) != 0:
print(f" FAILED Horner != sum-of-powers at n={n}")
full_ok = False
break
if full_ok:
print(" PROVED Horner(X_1..X_n) == sum_i X_i H^(n-i+1) for n = 0..15 (symbolic)")
return ok and full_ok
def prove_ctr_inductive():
try:
from z3 import BitVec, BitVecSort, Function, Implies, Solver, unsat
except ImportError:
print(" z3 not installed: pip install z3-solver", file=sys.stderr)
return False
B = BitVecSort(128)
inc = Function("inc", B, B) E = Function("E", B, B)
def check(name, claim):
s = Solver()
s.add(claim == False) r = s.check()
ok = r == unsat
print(f" {'PROVED ' if ok else 'FAILED '} {name}")
return ok
ok = True
init = BitVec("init", 128)
cs_i = BitVec("cs_i", 128)
co_i = BitVec("co_i", 128)
ok &= check(
"CTR counter step: equal counters at block i => equal at i+1 (inc is a function)",
Implies(cs_i == co_i, inc(cs_i) == inc(co_i)),
)
ok &= check("CTR counter base: seal and open both start at init", init == init)
p_i = BitVec("p_i", 128) ctr_i = BitVec("ctr_i", 128) ks = E(ctr_i)
c_i = p_i ^ ks p_rec = c_i ^ E(ctr_i) ok &= check("CTR block i: open recovers seal's plaintext block (any i, any n)",
p_rec == p_i)
if ok:
print(" => by induction on the block index, the whole n-block CTR pass")
print(" round-trips for every n (counters coincide at every block,")
print(" and each block is independently recovered).")
return ok
def main():
print("1. GHASH/POLYVAL Horner == sum-of-powers, for all n (induction) ...")
a = prove_horner_inductive()
print("\n2. CTR round-trips for all n (counter induction + block bijection) ...")
b = prove_ctr_inductive()
if a and b:
print("\nPROVED for an arbitrary number of blocks: the GHASH accumulator is")
print("the specified sum-of-powers, and CTR decryption inverts encryption,")
print("for every n - lifting the representative-block proofs to all inputs.")
return 0
print("\nFAILED")
return 1
if __name__ == "__main__":
sys.exit(main())