Repository navigation
pykyber V2 - #44
Merged
Merged
pykyber V2#44
Conversation
Adds PLAN-2.0.md (work packages for issue #3), AGENTS.md (rules for coding agents), CLAUDE.md and opencode.json so both Claude Code and opencode load the same project instructions. All 2.0 work targets dev-v2 and is rebase-merged; every commit must pass on its own, and review fixes are folded into the commit they fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.0 development happens on dev-v2 (issue #4); merges into it must be built for every platform before the 2.0.0 release. pull_request already covers PRs into any branch, so only the push trigger was missing.
Setup, build/test commands, the 2.0 branch/commit rules (dev-v2, wp<N>-* branches, Conventional Commits, fixup folding, rebase-merge so every commit passes on its own), and the security reporting pointer (issue #4).
PR template carries the constant-time/security checklist from AGENTS.md so it is in front of every PR, not only agent-written ones. CODEOWNERS routes src/ and SECURITY.md to the maintainer for review (issue #4).
Per WP-0 (issue #4). blank_issues_enabled: false plus a contact link routing security reports to GitHub private vulnerability reporting, since a public security issue template would defeat the point of private advisories; bug/feature templates carry the same pointer.
Root security policy per WP-0 (issue #4): reports go through GitHub private vulnerability reporting (enabled), never public issues; scope, disclosure, and response expectations stated. Migrates the six resolved audit items from .opencode/SECURITY.md (gitignored, so invisible to contributors) verbatim, with a provenance note. The original stays in place as the internal log.
Agent-written PR bodies reached GitHub with broken characters this week (mojibake from non-ASCII) and one read like generated output, so the rules follow: prose for the reviewer with the facts and evidence, a 7-bit ASCII byte check before anything is posted, and the REST API fallback plus body read-back for the GraphQL failures the gh CLI hits in this repo.
test_kyber_1024_params asserted the Kyber512/768 compression parameters (du=10, dv=4) for Kyber1024, which compresses with du=11, dv=5 per the Kyber round-3 specification (2021-08-04), Sec. 1.4, Tables 1-2, and FIPS 203 final, Sec. 8, Tables 2-3 (identical sizes: pk 1568, sk 3168, ct 1568). PolyVec-compressed bytes are 4*352 = 1408 and the ciphertext is 4*352 + 160 = 1568 bytes. The constants in src/kyber/params.rs were already correct: only the test assertions were wrong (both the first failing one, POLYVECCOMPRESSEDBYTES = 4*320, and CIPHERTEXTBYTES = 4*320+128). No runtime behavior changes; Kyber v1.0.5 output is unaffected. Fixes #5
Freeze the byte output of pykyber v1.0.5 (round-3 Kyber) for seeded keygen, seeded encapsulation via the Rust crypto_kem path at tag v1.0.5, and decapsulation. Captured from the pinned PyPI wheel (sha256 recorded in the file meta) plus a cargo --release run in a git worktree at tag v1.0.5; both layers cross-checked equal by tests/vectors/generate.py. The vector file is append-only: never regenerate or edit it to make a test pass.
tests/legacy_vectors.rs replays seeded keygen, seeded encapsulation and decapsulation from the append-only vector file and asserts byte equality with v1.0.5; the rng guard panics if any seeded path consumes randomness. python/tests/test_legacy_vectors.py checks the same bytes through the Kyber512/768/1024 wrappers (seeded keygen, decapsulate, from_keys, and encapsulate/decapsulate roundtrip over v1.0.5 key material). serde and serde_json are dev-only dependencies; Cargo.lock is left untouched (its cleanup stays with the owed chore PR).
This reverts commit c25560d.
This reverts commit 4d85c27.
Freeze the byte output of pykyber v1.0.5 (round-3 Kyber) for seeded keygen, seeded encapsulation via the Rust crypto_kem path at tag v1.0.5, and decapsulation. Captured from the pinned PyPI wheel (sha256 recorded in the file meta) plus a cargo --release run in a git worktree at tag v1.0.5; both layers cross-checked equal by tests/vectors/generate.py. The vector file is append-only: never regenerate or edit it to make a test pass.
tests/legacy_vectors.rs replays seeded keygen, seeded encapsulation and decapsulation from the append-only vector file and asserts byte equality with v1.0.5; the rng guard panics if any seeded path consumes randomness. python/tests/test_legacy_vectors.py checks the same bytes through the Kyber512/768/1024 wrappers (seeded keygen, decapsulate, from_keys, and encapsulate/decapsulate roundtrip over v1.0.5 key material). serde and serde_json are dev-only dependencies; Cargo.lock is left untouched (its cleanup stays with the owed chore PR).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fixed where the change is plainly behavior-preserving: - fips202: add Default for KeccakState, use u64::rotate_left, parenthesise shift amounts - kem, indcpa: drop needless borrows - poly: KYBER_Q.div_ceil(2) for the public rounding constant Allowed with a comment instead of rewriting hot-path code: - needless_range_loop, identity_op, explicit_counter_loop in poly, polyvec, indcpa (and one loop in ntt): the loops mirror the reference C layout - manual_is_multiple_of in fips202: is_multiple_of needs Rust 1.89 and no MSRV is declared Legacy output is unchanged: tests/legacy_vectors.rs and python/tests/test_legacy_vectors.py pass untouched. A single commit because each lint-kind commit alone would still fail clippy. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eck.sh requirements-dev.txt pins the ranges (pytest <9 keeps Python 3.8 usable). scripts/check.sh is the fmt, clippy, test, maturin develop, pytest gate that CI will run per commit. CONTRIBUTING.md now points at both and drops the stale baseline-failures note. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adds a test job (Linux, macOS, Windows; Python 3.8 and newest 3.x) and a commits job that runs scripts/check.sh through git rebase --exec on each commit of a PR, since PRs are rebase-merged. The wheel release now needs the test job. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Literal transcriptions of Algorithms 5, 8, 9, 10, 11 and Compress_d / Decompress_d, plus hashlib-derived SHA-3/SHAKE vectors, compared with the legacy Keccak, NTT, CBD and poly encoding code. Also records that legacy d=11 compression deviates from Compress_11 at two residues. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
G(m || H(ek)) and J(z || c) in ML-KEM hash secret inputs, and FIPS 203 section 3.3 requires intermediates to be destroyed. The shake paths already zeroize on drop through KeccakState; these two one-shot hashes kept the state in a plain array. Output is unchanged (legacy golden vectors pass). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ParamSet<K> derives every FIPS 203 Table 2 parameter and the Table 3 sizes from k, with aliases for the three standard sets, so the new code does not need another _512/_768/_1024 copy. PolyVec<K> wraps the legacy Poly NTT, reduction and ByteEncode12 routines and is tested against the three legacy polyvec types. from_bytes reduces mod q like ByteDecode12; the legacy poly_frombytes does not. The module is crate-private and unused until WP-3. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Algorithm 7 over the shared SHAKE128 XOF. rej_uniform becomes pub(crate) (no body change). The test compares against a line-by-line transcription of the algorithm over 3000 seeds, including inputs that need more than the initial three blocks. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The legacy d=11 routine is off by one at the residues 1875 and 3125 and cannot change without breaking the Kyber-1024 golden vectors (checked by applying the fix temporarily: legacy_kyber_v1_0_5_outputs_are_frozen fails). ML-KEM gets its own, using ((x << 11) + 1664) * 645084 >> 31, which the unit test compares with FIPS 203 (4.7) for every residue and every negative representative. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… jobs Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
poly_compress_d11 becomes poly_compress(r, a, d) with a public width d, so the du, dv and message paths of K-PKE can all use one exact, division-free routine instead of the legacy per-level loops. d is a runtime argument because stable Rust cannot pass ParamSet's associated constants as const generics. The multiplier is checked against FIPS 203 (4.7) for every residue and every negative representative for d = 1, 4, 5, 10 and 11. The test that d = 11 differs from the legacy routine only at 1875 and 3125 is kept. No legacy code changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
poly_decompress is ByteDecode_d followed by Decompress_d from the (y * q + 2^(d-1)) >> d form, exact over all 2^d inputs for the widths ML-KEM uses. poly_from_msg is Decompress_1(ByteDecode_1(m)) with the all-ones or all-zeros mask routed through black_box, replacing the legacy poly_frommsg pattern that CVE-2024-37880 hit. Neither touches legacy code. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
All four file kinds (seed decapsulation, encapsulation, key generation from a seed, semi-expanded decapsulation) for the three parameter sets, pinned to a commit and a SHA-256 per file in the manifest. The result rule of the schema is applied as written. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A dev-only differential test (ml-kem 0.3.3, FIPS 203 final) over a fixed-seed generator: keygen from a seed, encaps with a given m, decaps of valid, bit-flipped and random ciphertexts, decaps under a changed dk_PKE or z, and the modulus and hash checks after random byte changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The mask built from a message bit in the legacy poly_frommsg is the pattern behind CVE-2024-37880 (Clangover), where a compiler turned it into a branch. Route the bit and the mask through core::hint::black_box. Output is unchanged; the golden vectors pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Route the accumulated difference, the flag and the select mask through core::hint::black_box, the same idea as mlkem::ct, so the compiler cannot prove the mask is 0 or 0xff and turn the select into a branch. The release disassembly shows setne/cset and a vector select with no branch on the compare result. Output is unchanged; the golden vectors pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Key generation and encapsulation in the Python bindings used thread_rng(). Use rand::rngs::OsRng, call try_fill_bytes in the legacy KEM functions, and return KyberError::RandomBytesGeneration when the generator fails instead of panicking. Wipe the half-built secret key when the second draw (z) fails and hold the partly filled seed in Zeroizing. The Python wrappers keep the RandomnessError subclass instead of re-wrapping it. Seeded paths and the golden vectors are unchanged; the random-fill order and sizes are as before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Hold m', the KDF input and the re-encrypted ciphertext of crypto_kem_dec* in Zeroizing so they are wiped on every exit, unwinding included, and keep the implicit-rejection flag in Zeroizing<u8>. The PRF output buffers in poly_getnoise_* are the secret noise (the key in key generation, r in encryption) and were never wiped; wipe them too. The release disassembly shows the wipe of the flag and the buffers on both the normal and the cleanup path. Add a test for the implicit-rejection output, derived from z and H(c), for all three levels; it also passes on the commit before this series. Output is unchanged and the golden vectors pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the secret key Kyber512/768/1024.from_keys accepted any public key and secret key of the right sizes, so a pair from two different key pairs built a keypair whose decapsulate silently returned a different secret than encapsulate had produced. The round-3 secret key embeds the public key (sk_pke || pk || H(pk) || z), so compare the two (both are public) and raise KyberError on a mismatch. The offset comes from named constants. A wrong-size secret key keeps its existing size error at use. Approved by the maintainer in the discussion on issue #18; ML_KEM_*.from_keys is not changed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The legacy bindings returned secret keys and shared secrets as Vec<u8>, which PyO3 turns into bytes and then frees with the secret still in the allocation. Hand them over as a SecretBytes wrapper that builds the bytes object and then wipes the Rust buffer, and wipe the fixed-size copy from the safe API. The Python object itself cannot be wiped. Output is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The seeded derive path hands the KEM code a stand-in generator that is never asked for bytes. Now that the legacy code draws randomness through try_fill_bytes, make that method return an error, so a future change that reaches it gets RandomBytesGeneration and not a panic. No path from Python reaches it today. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ation functions crypto_kem_keypair* and crypto_kem_enc* indexed caller-supplied pk, sk, ct, ss and seed slices without checking their lengths, unlike decapsulation, so a Rust caller with a short buffer panicked. Return InvalidInput instead. The Python bindings already size every buffer, so no Python path reaches this. Output is unchanged; the golden vectors pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lockfile-only patch bump of an existing dependency. The advisory needs the log feature, which pykyber does not enable, so it was not reachable here; the legacy bindings no longer use thread_rng at all. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…TY.md Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXWjkNs3Htog36snV9FcXb
The old benchmark parsed the Kyber performance tests only and its figures came from a debug build. benchmark.py now times ML_KEM_* and Kyber* directly (median of 7 rounds of 500 calls), writes results.json, and draws two charts that replace the four old ones. make benchmark builds the release extension first. The chart dependency is in performance-tests/requirements.txt, not in requirements-dev.txt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXWjkNs3Htog36snV9FcXb
Same facts, fewer words: install, quick start, a shorter class tour, sizes, errors, the Kyber deprecation, security status and the new benchmark table and charts. Every Python block in it was run, and the error message it shows is the real one. The performance section now says what was measured and on what. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXWjkNs3Htog36snV9FcXb
Adds LICENSE-MIT and LICENSE-APACHE (the Apache text is the apache.org file, 11358 bytes, SHA-256 cfc7749b...523d30) and removes LICENSE. Sets license = "MIT OR Apache-2.0" in Cargo.toml and pyproject.toml, with license-files, and drops the Public Domain classifier. The built wheel's METADATA has License-Expression: MIT OR Apache-2.0 and ships both files. Releases up to 1.0.5 stay under the Unlicense; the README, CONTRIBUTING.md, the changelog and the agent notes say so. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXWjkNs3Htog36snV9FcXb
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ents file Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.