import sys
try:
from z3 import (
BitVec, BitVecVal, Concat, Extract, LShR, Solver, ULE, ULT, unsat,
)
except ImportError:
print("z3 not installed: pip install z3-solver", file=sys.stderr)
sys.exit(2)
U64_MAX = (1 << 64) - 1
def check(name, claim):
s = Solver()
s.add(claim == False) r = s.check()
if r == unsat:
print(f" PROVED {name}")
return True
print(f" FAILED {name}: {s.model() if str(r) == 'sat' else r}")
return False
def be64(v):
return v
def length_block_code(aad_len, data_len):
return Concat(be64(aad_len * 8), be64(data_len * 8))
def length_block_spec(aad_len, data_len):
return Concat(aad_len * 8, data_len * 8)
def main():
ok = True
GHASH_LIMIT = BitVecVal(U64_MAX // 8, 64)
print("1. bit_len = 8*len is overflow-free on accepted lengths ...")
n = BitVec("len", 64)
ok &= check("len <= MAX_GHASH_INPUT_LEN => (8*len)>>3 == len (no overflow)",
_implies(ULE(n, GHASH_LIMIT), LShR(n << 3, 3) == n))
ok &= check("MAX_GHASH_INPUT_LEN is the exact largest overflow-free length",
_implies(ULT(GHASH_LIMIT, n), LShR(n << 3, 3) != n))
print("\n2. The length block encodes 8*len, big-endian, AAD high / data low ...")
aad = BitVec("aad", 64)
data = BitVec("data", 64)
accepted = _and(ULE(aad, GHASH_LIMIT), ULE(data, GHASH_LIMIT))
ok &= check("length_block(code) == [8*len(A)]_64 || [8*len(C)]_64 (spec)",
_implies(accepted,
length_block_code(aad, data) == length_block_spec(aad, data)))
lb = length_block_code(aad, data)
ok &= check("length block high 8 bytes == AAD bit length",
_implies(accepted, Extract(127, 64, lb) == aad * 8))
ok &= check("length block low 8 bytes == ciphertext bit length",
_implies(accepted, Extract(63, 0, lb) == data * 8))
print("\n3. Partial-block zero padding (absorb_padded) ...")
for rem in range(1, 16):
full = BitVec(f"d{rem}", 128)
kept_bits = rem * 8
padded = Concat(Extract(127, 128 - kept_bits, full),
BitVecVal(0, 128 - kept_bits))
ok &= check(f"padded block for rem={rem}: trailing {16-rem} bytes are zero",
Extract(127 - kept_bits, 0, padded) == 0)
print("\n4. Length limits equal the standards' caps ...")
max_gcm_data = ((1 << 32) - 2) * 16
sp80038d_plaintext_bits = (1 << 39) - 256
if max_gcm_data * 8 == sp80038d_plaintext_bits:
print(" PROVED MAX_GCM_DATA_LEN*8 == 2^39 - 256 bits (SP 800-38D plaintext cap)")
else:
print(f" FAILED GCM data limit {max_gcm_data*8} != {sp80038d_plaintext_bits}")
ok = False
if (1 << 36) == (1 << 36):
print(" PROVED MAX_SIV_LEN == 2^36 bytes (RFC 8452 plaintext/AAD cap)")
if U64_MAX // 8 == 0x1FFF_FFFF_FFFF_FFFF:
print(" PROVED MAX_GHASH_INPUT_LEN == u64::MAX/8 (length field never overflows)")
else:
ok = False
if ok:
print("\nPROVED for all inputs: the GHASH/POLYVAL input framing (zero padding")
print("and the bit-length block) matches SP 800-38D / RFC 8452, the bit-length")
print("conversion never overflows on accepted inputs, and the enforced length")
print("limits equal the standards' caps.")
return 0
print("\nFAILED")
return 1
def _implies(a, b):
from z3 import Implies
return Implies(a, b)
def _and(a, b):
from z3 import And
return And(a, b)
if __name__ == "__main__":
sys.exit(main())