Pqc engines - #3
Merged
Merged
Conversation
added 15 commits
June 25, 2026 13:20
Implements Keccak-f[1600] and the sponge construction from scratch — no OpenSSL, no external dependencies. Provides SHA3-256, SHA3-512, SHAKE128, SHAKE256. All four are verified against 9 NIST FIPS 202 known-answer vectors (block-boundary cases included). SHA-3 is a dependency of SPHINCS+ and is the pre-image of Ethereum's keccak256. - cpp/sha3/sha3.h — public API (Digest256/512, SHAKE as vector<uint8_t>) - cpp/sha3/sha3.cpp — Keccak-f[1600] permutation + sponge absorb/squeeze - cpp/sha3/sha3_test.cpp — 9 NIST vectors across SHA3-256/512, SHAKE128/256 - CMakeLists.txt — sha3 static library + sha3_test ctest target - CI label updated to reflect both FIPS 180-4 and FIPS 202 coverage
Post-quantum hash-based digital signatures built directly on the SHA-3 module added in the previous commit. Implements the full SLH-DSA-SHAKE-128s parameter set (n=16, h=63, d=7, k=14, a=12, w=16) from scratch with no external crypto library. Scheme layers: - WOTS+: one-time signatures via LEN=35 hash chains of length w=16 - XMSS: Merkle tree of 512 WOTS+ public keys per layer - HT: 7-layer hypertree; each layer signs the root below - FORS: 14 few-time trees (height 12) signing the message digest indices All tweakable hash functions call SHAKE256(PK.seed || ADRS || input) with a 32-byte address struct (ADRS) for domain separation. Signing is randomised by default (/dev/urandom) or deterministic via opt_rand. Also optimises Keccak-f[1600] (eliminates %5 modulo in theta/chi) and adds a word-level absorb fast-path in the sponge, reducing SHAKE256 call overhead by ~3x. SHA-3 KAT vectors continue to pass. Signature size: 7856 bytes. Round-trip sign/verify: ~4.5s single-threaded. 11/11 tests pass (keygen, sign, verify, tamper rejection, determinism).
- fors_indices: uint8_t -> uint16_t — 12-bit FORS indices (A=12) were silently truncated to 8 bits, collapsing the reachable leaf space from 4096 to 256 per tree and reducing effective FORS security from 168 to 112 bits (below the 128-bit target). Sign/verify remained consistent so tests passed, but the security margin was wrong. - prf_msg, h_msg: add size_t overflow guard before vector allocation — a near-SIZE_MAX msg_len wraps the addition, allocates a tiny buffer, and the subsequent memcpy writes past it. - random_bytes: check gcount() after read() — a short read from /dev/urandom left the tail of opt_rand as zeros without any error. - thash: assert(in_len <= LEN*N) — the 608-byte stack buffer is sized for the largest valid input; make that contract explicit. - fors_sign_and_pk: set_tree_height(0) explicitly on FORS_PRF ADRS — previously relied on zero-initialised base_adrs; defensive explicit set matches what fors_leaf already does. - sha3.cpp: static_assert little-endian — the fast-absorb word-XOR path is only correct on LE hosts; make this a compile-time failure on BE.
std::endian is C++20; use __BYTE_ORDER__ preprocessor macros instead, guarded by #if so the check is skipped on compilers that don't define them.
Implements the ML-KEM-512 parameter set (k=2, q=3329, n=256, η1=3, η2=2,
du=10, dv=4) — the 128-bit post-quantum KEM standardised as FIPS 203.
Core components:
- NTT and InvNTT over Z_q[X]/(X^256+1) with precomputed zeta table
- BaseCaseMul for NTT-domain pointwise multiplication via 128 quadratic
factors; GAMMAS[i] = 17^{2·BitRev7(i)+1}
- ByteEncode/ByteDecode (little-endian bit packing, any d)
- Compress/Decompress (rounding to d bits)
- SampleNTT (SHAKE128 rejection sampling), SamplePolyCBD, PRF (SHAKE256)
- K-PKE.KeyGen, K-PKE.Encrypt, K-PKE.Decrypt (Algorithm 13-15)
- ML-KEM.KeyGen, ML-KEM.Encaps, ML-KEM.Decaps with implicit rejection
(Algorithm 16-18)
SHA-3 changes:
- Add shake128_into() and shake256_into() — allocation-free XOF helpers
needed by SampleNTT and the implicit-rejection path
12/12 round-trip and rejection tests pass.
FIPS 203 Algorithm 13 (K-PKE.KeyGen) defines the public matrix as A_hat[i][j] = SampleNTT(XOF(ρ, j, i)) — j before i. Our code had the indices swapped, so the generated matrix was the transpose of the correct one. Algorithm 14 (K-PKE.Encrypt) must use A^T, which under the fixed convention is SampleNTT(XOF(ρ, i, j)) — i before j. Both sides were consistently wrong, so round-trips passed, but outputs did not match NIST ACVP vectors. Verified fix against NIST ACVP ML-KEM-keyGen-FIPS203 tcId=1: ek[0..31] now matches expected value. Also adds a full KAT test block: keygen ek is validated against NIST ACVP tcId=1; encaps ct/ss are golden values for regression coverage. All 16 tests pass.
The function comment said "(i, j)" in a way that implied param i = FIPS row and param j = FIPS col. After the matrix index fix, keygen calls (j, i) and encrypt calls (i, j) — a maintainer reading the old comment would conclude the keygen call site looks transposed and "fix" it, silently reverting the FIPS 203 Alg 13 correction. New comment makes the byte-order contract explicit and names both call sites inline.
'A'-'F' were routed to the lowercase branch, producing c-'a'+10 = -22 cast to uint8_t = 234 — silently corrupting any uppercase nibble. Adds an uppercase branch so NIST ACVP JSON vectors (which use uppercase A-F) can be pasted in directly without silent corruption.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.