94d5f176f7
feat(crypto): portable constant-time AES-GCM software fallback (rv2 8 phase C)
...
- no-intrinsics AES: S-box = GF(2^8) inverse via a fixed-exponent power ladder
(constant-time in the input, no tables), constant-time gf8_mul, byte-oriented
ShiftRows/MixColumns/key-expansion (AES-128 and AES-256)
- constant-time GHASH: bit-by-bit GF(2^128) multiply (mask-driven, no tables)
- aes_gcm_seal/open now dispatch: AES-NI path when present (and not forced
software), else this portable fallback -> AES-GCM works on ANY CPU, so the
phase-B no-AES-NI trap is retired
- wo_aes_force_software test hook; both hw and sw paths verified against NIST
SP 800-38D cases 4 (AES-128) and 16 (AES-256) byte-for-byte; test_crypto 48/0;
ASan/UBSan clean; full runtime battery green
- ARMv8 crypto-extension hardware path deferred (untestable on x86-64 host)
(cherry picked from commit dccf650899798401a9adac8489f34c85ed9304af)
2026-09-15 01:15:31 +02:00
1ef4e463ec
feat(crypto): AES-GCM via AES-NI + PCLMULQDQ (rv2 8 phase B, ids 113/114)
...
- aes_gcm_seal/open, AES-128 and AES-256 (variant by key length 16/32),
nonce 12 bytes, out = ciphertext||tag; open returns nil on auth failure
- hardware path only (phase B): AES-NI key schedule (128/256) + block, GHASH
via PCLMULQDQ with the fast GF(2^128) reduction, GCM mode (J0, CTR from
counter 2, GHASH over aad|pad|ct|pad|len, tag = GHASH ^ AES(J0))
- constant-time by hardware; target-attributed functions + __builtin_cpu_supports
gate so the binary stays portable -- no AES-NI traps with a clear message
(bitsliced software + ARMv8 paths are phase C)
- wiring: wob.h ids + WO_B_MAX 114; builtin.c crypto range; loader arity 4;
emit.ml (ids/arity/return/name); types.ml (register + return type)
- VERIFIED: matches NIST SP 800-38D cases 4 (AES-128) and 16 (AES-256) and the
python cryptography reference byte-for-byte; KAT-gated in test_crypto (36/0);
ASan/UBSan clean; runtime battery + compiler 557/0 green
(cherry picked from commit f12a745a3c1313847f9d7f65e53bcd8093758af9)
2026-09-15 01:15:31 +02:00
ac52c3fdb5
feat(crypto): ChaCha20-Poly1305 AEAD (rv2 8 phase A, ids 111/112)
...
- hand-rolled ChaCha20 + poly1305-donna-32 + RFC 8439 §2.8 AEAD in crypto.c;
constant-time (add/xor/rotate + limb math, no tables, no data-dep branches),
constant-time tag compare
- two bare-name crypto-family builtins beside sha256/hmac:
chacha20poly1305_seal(key,nonce,aad,pt) -> Bytes (ct||tag)
chacha20poly1305_open(key,nonce,aad,ct||tag) -> ?Bytes (nil on auth fail)
key 32B, nonce 12B (caller-supplied, per TLS's per-record nonce need)
- wiring: wob.h enum + WO_B_MAX 112; builtin.c crypto dispatch range; loader.c
arity 4; emit.ml (ids, arity_of 4-case, return type, is_builtin_name,
name->id); types.ml (registration + return type)
- VERIFIED: matches RFC 8439 §2.8.2 byte-for-byte (vs python cryptography +
the RFC vector); test_crypto 24/0 (Poly1305 §2.5.2 + AEAD seal/open/tamper);
ASan/UBSan clean; runtime battery + compiler 557/0 green
- first rung of the TLS ladder (rv2 9 phase A)
(cherry picked from commit 961854a8f4e9e632b6fa17f7f2e519e2d08f4936)
2026-09-15 01:15:31 +02:00
5fc32b4926
feat: iteration 34 — digest builtins sha1/sha256/hmac_sha256 (ids 85-87)
...
- runtime/src/crypto.c: hand-rolled cores, whole-value over Bytes,
allocation-free tails; VM half returns fresh Bytes, WO_T_BOUNDS on
wrong class id (Bytes builtins' message shape)
- test_crypto: RFC 3174 + FIPS 180-4 + RFC 4231 (incl. long-key case 6)
+ 63/64/65 block-boundary sweep + the RFC 6455 handshake input, 18/0
- compiler surface flat per house convention (sha1, not crypto.sha1 —
matches base64_encode): types.ml signatures + result types, emit.ml
ids/dispatch/arity/known-list/drop-table (fresh-Bytes entries so
results get their drops)
- corpus run/crypto-digests pins the .wo path through base64_encode
- no .wob bump (ticks-84 precedent); WO_B_MAX 87; surface doc rows
- slice marker + board row: iteration 24 (absorbing 31+34) executing
- battery 12/12 fresh-built
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 00:42:43 +02:00