Skip to content
Merged

Dev #45

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
c47c208
Scaffolding for the rust python package
Zikithezikit Feb 20, 2026
1846068
Working hints
Zikithezikit Feb 20, 2026
54a0d98
Better file struct + Makefile
Zikithezikit Feb 20, 2026
730f83f
working kyber
Zikithezikit Feb 20, 2026
cd46c3c
added tests.
Zikithezikit Feb 20, 2026
23e87c2
More verbose funcion and var names
Zikithezikit Feb 20, 2026
c1b5a23
changed public api
Zikithezikit Feb 20, 2026
0a52dc6
changed public api again
Zikithezikit Feb 20, 2026
9e16c47
changed public api again v2
Zikithezikit Feb 20, 2026
febab8a
Added 512, 1024 support
Zikithezikit Feb 20, 2026
1ff3274
Better rust testing
Zikithezikit Feb 20, 2026
fca21b1
Better README.md
Zikithezikit Feb 20, 2026
f8736cc
Ready for PyPI
Zikithezikit Feb 20, 2026
0e71ae1
Add Python 3.8+ support with abi3
Zikithezikit Feb 21, 2026
611722a
v0.1.2 - Add Python 3.8+ support and Termux (aarch64, armv7)
Zikithezikit Feb 21, 2026
03e0d08
lsp syntax support
Zikithezikit Feb 22, 2026
857fcf1
initial proper error handling in python
Zikithezikit Feb 22, 2026
e445d4d
more panic tests and error handling
Zikithezikit Feb 22, 2026
2780216
more verbose errors
Zikithezikit Feb 22, 2026
0cc07ae
Better python file structure
Zikithezikit Feb 22, 2026
36b5fe0
Bump version to 0.1.4
Zikithezikit Feb 22, 2026
80c0411
Add error handling section to README
Zikithezikit Feb 22, 2026
6a7228b
Bump version to 0.1.5
Zikithezikit Feb 22, 2026
f886d70
Added new decapsulate feature
Zikithezikit Feb 23, 2026
f726e39
Added stress tests and grpahs from it
Zikithezikit Feb 23, 2026
1cb2743
Added backwards compatibility
Zikithezikit Feb 25, 2026
1a50225
version bump
Zikithezikit Feb 25, 2026
e26d2a3
version bump
Zikithezikit Feb 25, 2026
c439291
feat: add deterministic key generation and serialization utilities
Zikithezikit Mar 10, 2026
ab9bd57
docs: add invalid seed size to common error cases in README
Zikithezikit Mar 10, 2026
0e495e1
security: implement memory zeroing and harden cryptographic primitives
Zikithezikit Mar 10, 2026
8a6de50
feat: implement comprehensive security hardening and compliance fixes
Zikithezikit Mar 10, 2026
0fe47e5
Merge pull request #2 from Zikithezikit/feature/deterministic-keys-se…
Zikithezikit Mar 10, 2026
6a4de71
Bump version to 0.2.0
Zikithezikit Mar 10, 2026
2703cac
Release v1.0.0
Zikithezikit Jun 6, 2026
2b0b422
v1.0.1: fix PyPI classifier for Unlicense
Zikithezikit Jun 6, 2026
fee06d9
v1.0.2: bump version
Zikithezikit Jun 6, 2026
f0d2687
v1.0.3: restore PyPy wheels in CI
Zikithezikit Jun 6, 2026
f451353
v1.0.5: add Android armv7 + multi-version Python wheels
Zikithezikit Jun 6, 2026
7597bb1
fix: bump linux-pypy manylinux to 2_34, remove _PYTHON_SYSCONFIGDATA_…
Zikithezikit Jun 6, 2026
23cfb7a
fix: use target-prefixed clang (NDK r27d removed generic clang)
Zikithezikit Jun 6, 2026
583ab88
fix: use versioned clang binary directly (NDK r27d wrappers still ref…
Zikithezikit Jun 6, 2026
05ee55d
fix: search for clang-* (versioned) with hyphen; NDK r27d clang binar…
Zikithezikit Jun 6, 2026
a6bae76
fix: add --ld-path for clang (NDK r27d removed ld.lld symlink)
Zikithezikit Jun 6, 2026
07b125f
fix: recreate ld.lld symlink (removed in NDK r27d); lld needs argv[0]…
Zikithezikit Jun 6, 2026
c5fa21c
fix: restore clang/clang++/ld.lld symlinks removed in NDK r27d
Zikithezikit Jun 6, 2026
284cc05
fix: add libpython3.so symlink for pyo3's -lpython3 linker flag
Zikithezikit Jun 6, 2026
e065e8d
fix: set ANDROID_API_LEVEL env var for maturin
Zikithezikit Jun 6, 2026
cca7998
docs: add 2.0 plan and agent instructions
Zikithezikit Oct 29, 2026
f2d0a6b
ci: run CI on pushes to dev-v2
Zikithezikit Oct 29, 2026
bd94565
docs: add CONTRIBUTING.md
Zikithezikit Oct 29, 2026
aa2d78d
ci: add pull request template and CODEOWNERS
Zikithezikit Oct 29, 2026
29b0e16
ci: add issue templates (bug, feature, security redirect)
Zikithezikit Oct 29, 2026
af58acd
security: add SECURITY.md with reporting policy and findings log
Zikithezikit Oct 29, 2026
3cc516b
docs: note 2.0.0 repo setup in changelog
Zikithezikit Oct 29, 2026
b0bde8e
docs: add PR-description rules for agents to AGENTS.md
Zikithezikit Oct 29, 2026
538a7d4
test: fix Kyber-1024 compression byte sizes per Kyber round-3 spec
Zikithezikit Oct 29, 2026
8f801ce
test: capture legacy Kyber golden vectors from v1.0.5
Zikithezikit Oct 29, 2026
0920388
test: check current Kyber output against v1.0.5 golden vectors
Zikithezikit Oct 29, 2026
467b747
Revert "test: check current Kyber output against v1.0.5 golden vectors"
Zikithezikit Oct 30, 2026
0a412b2
Revert "test: capture legacy Kyber golden vectors from v1.0.5"
Zikithezikit Oct 30, 2026
dc8df2b
test: capture legacy Kyber golden vectors from v1.0.5
Zikithezikit Oct 29, 2026
609b6e2
test: check current Kyber output against v1.0.5 golden vectors
Zikithezikit Oct 29, 2026
9d6b213
docs: refresh CLAUDE.md test baseline after legacy vector freeze
Zikithezikit Oct 30, 2026
aacc48d
refactor: make cargo clippy --all-targets -D warnings pass
Zikithezikit Oct 30, 2026
60f33ae
build: declare maturin and pytest as dev dependencies, add scripts/ch…
Zikithezikit Oct 30, 2026
bddebe0
ci: run tests and lints on every PR and on every commit of a PR
Zikithezikit Oct 30, 2026
4607c48
test: check the primitives ML-KEM would share against FIPS 202/203
Zikithezikit Oct 30, 2026
b51322f
security: zeroize the Keccak state in sha3_256 and sha3_512
Zikithezikit Oct 30, 2026
a688c0d
feat: add ML-KEM parameter type and PolyVec generic over k
Zikithezikit Oct 30, 2026
1ad41bb
feat: add SampleNTT on top of the legacy rej_uniform
Zikithezikit Oct 30, 2026
6c80000
feat: add an exact Compress_11 for ML-KEM-1024
Zikithezikit Oct 30, 2026
6caab7a
docs: record which legacy primitives ML-KEM shares and which it does not
Zikithezikit Oct 30, 2026
351a955
docs: refresh CLAUDE.md baseline after the clippy cleanup and CI test…
Zikithezikit Oct 30, 2026
cf24bf5
docs: note that CI clippy runs on the latest stable toolchain
Zikithezikit Oct 30, 2026
91fa420
docs: ask agents to use named constants instead of magic numbers
Zikithezikit Oct 30, 2026
e9ce6d9
refactor: make the exact Compress_d generic over d
Zikithezikit Oct 30, 2026
9cd214e
feat: add Decompress_d and a branch-free message decoder for ML-KEM
Zikithezikit Oct 30, 2026
38d9cb0
feat: add K-PKE KeyGen, Encrypt and Decrypt for ML-KEM
Zikithezikit Oct 30, 2026
4e07f45
test: add a fetch script for pinned external test vectors
Zikithezikit Oct 30, 2026
db45cf4
test: check K-PKE against the NIST ACVP ML-KEM vectors
Zikithezikit Oct 30, 2026
792e1ba
feat: add error variants for failed ML-KEM key checks
Zikithezikit Oct 30, 2026
99b9f0e
feat: add input checks and a constant-time reject select for ML-KEM
Zikithezikit Oct 30, 2026
984cf46
feat: add ML-KEM KeyGen, Encaps and Decaps (FIPS 203 Algorithms 16-21)
Zikithezikit Oct 30, 2026
f4816fa
test: run the real Decaps_internal and the key checks against ACVP
Zikithezikit Oct 30, 2026
fe596d2
refactor: build three ML-KEM size constants only for the tests that r…
Zikithezikit Oct 30, 2026
c4b40ec
feat: expose ML-KEM keygen, encaps and decaps to Python as private fu…
Zikithezikit Oct 30, 2026
cb9700d
feat: add ML_KEM_512, ML_KEM_768 and ML_KEM_1024 Python classes
Zikithezikit Oct 30, 2026
4e59564
test: run the NIST ACVP ML-KEM vectors through the Python API
Zikithezikit Oct 30, 2026
6b0ab8d
docs: describe the ML-KEM layers in CLAUDE.md
Zikithezikit Oct 30, 2026
37294f6
test: run the ACVP ML-KEM FIPS203-tr1 encapDecap vectors
Zikithezikit Oct 30, 2026
473b1a5
refactor: split K-PKE.KeyGen at the expansion of rho and sigma
Zikithezikit Oct 30, 2026
75ba07e
test: run the C2SP CCTV ML-KEM vectors in Rust
Zikithezikit Oct 30, 2026
d6c6f21
test: run the C2SP CCTV ML-KEM vectors through the Python API
Zikithezikit Oct 30, 2026
dc74dcb
test: run the C2SP Wycheproof ML-KEM vectors in Rust
Zikithezikit Oct 30, 2026
25647e2
test: run the C2SP Wycheproof ML-KEM vectors through the Python API
Zikithezikit Oct 30, 2026
0cb589f
test: compare ML-KEM with RustCrypto ml-kem on random inputs
Zikithezikit Oct 30, 2026
1059feb
docs: record the WP-4 vector setup in CLAUDE.md
Zikithezikit Oct 30, 2026
bd81256
feat: warn once per process on first use of a Kyber class
Zikithezikit Oct 30, 2026
398fe06
test: count Kyber deprecation warnings in subprocesses
Zikithezikit Oct 30, 2026
aa05f84
docs: add the Kyber to ML-KEM migration guide
Zikithezikit Oct 30, 2026
d715f45
docs: use ML-KEM in the README and API reference, mark Kyber deprecated
Zikithezikit Oct 30, 2026
d588794
chore: bump the version to 2.0.0 and add the 2.0.0 changelog entry
Zikithezikit Oct 30, 2026
0056ca0
security: put an optimization barrier around the poly_frommsg mask
Zikithezikit Oct 30, 2026
2a5fd9c
security: put an optimization barrier in legacy verify and cmov
Zikithezikit Oct 30, 2026
bd51aa4
security: take legacy Kyber randomness from OsRng and fail closed
Zikithezikit Oct 30, 2026
84cf094
security: wipe every secret intermediate of legacy decapsulation
Zikithezikit Oct 30, 2026
9081bc0
fix: make Kyber from_keys reject a public key that is not the one in …
Zikithezikit Oct 30, 2026
2e7b918
security: wipe the Rust copies of secrets returned to Python
Zikithezikit Oct 30, 2026
6c8cb15
fix: return an error from the unused DummyRng instead of panicking
Zikithezikit Oct 30, 2026
1799c73
fix: check buffer and seed lengths in the legacy keypair and encapsul…
Zikithezikit Oct 30, 2026
35e8495
chore: update rand to 0.8.6 for RUSTSEC-2026-0097
Zikithezikit Oct 30, 2026
118c336
docs: log the legacy Kyber security pass and the CVE review in SECURI…
Zikithezikit Oct 30, 2026
ba13dce
docs: benchmark ML-KEM on a release build and redraw the charts
Zikithezikit Oct 31, 2026
2e3115f
docs: shorten the README and rewrite it in plain prose
Zikithezikit Oct 31, 2026
e1009a3
chore: license under MIT OR Apache-2.0 instead of the Unlicense
Zikithezikit Oct 31, 2026
46d1053
docs: reduce the README to the essentials and add a guide for companies
Zikithezikit Oct 31, 2026
9b0ae14
docs: use the same Unlicense wording for 1.0.5 everywhere
Zikithezikit Oct 31, 2026
4b9cfc2
chore: remove the 2.0 plan, the opencode config and an empty requirem…
Zikithezikit Oct 31, 2026
99d5498
ci: remove the every-commit-passes job
Zikithezikit Oct 31, 2026
1874716
chore: merge main into dev, keeping the dev tree
Zikithezikit Oct 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# `cargo test` links the test binaries, and with pyo3's extension-module
# feature on macOS the Python symbols are left to the interpreter at load
# time. maturin passes these flags itself for wheels; plain cargo does not.
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]

[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
5 changes: 5 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Set-up per issue #4: the Rust crypto core and the security report are
# reviewed by the project owner. Everything arrives already authored and
# must be second-checked (constant-time hand crypto, no third-party crypto).
/src/ @Zikithezikit
/SECURITY.md @Zikithezikit
27 changes: 27 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: Bug report
about: Something in pykyber behaves differently from its documented or expected behavior
title: ''
labels: ''
assignees: ''
---

> **Security issue? Do not use this template.** [Private vulnerability reporting](https://github.com/Zikithezikit/pykyber/security/advisories/new) is the way to report anything that affects confidentiality/integrity, timing, or secret handling. Only file it publicly if you have already coordinated.

**Environment (please fill in):**

- pykyber version (`pip show pykyber`):
- Python version:
- Platform / architecture (e.g. Linux x86_64, Termux aarch64):

**What happened?**

<!-- Include a minimal, self-contained reproduction. Remove real keys, seeds, and shared secrets from any output before pasting it. -->

```python
# repro code here
```

**What did you expect to happen?**

<!-- For requests like "Kyber output differs between versions", compare against which reference? (link to the vector file, library version, or spec section) -->
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://github.com/Zikithezikit/pykyber/security/advisories/new
about: >-
Security problems (timing side channels, secret handling, incorrect
crypto behavior) must not be filed as public issues. Use GitHub's
private vulnerability reporting (see SECURITY.md at the repo root).
23 changes: 23 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest an addition to pykyber (crypto, API, packaging, docs)
title: ''
labels: ''
assignees: ''
---

> **Security issue? Do not use this template.** [Private vulnerability reporting](https://github.com/Zikithezikit/pykyber/security/advisories/new) is the way to report weaknesses. (An RFC-like proposal that happens to alter the crypto is fine here. A report of an exploitable flaw is not.)

**Is your feature request related to a problem?**

<!-- e.g. "I need to X but pykyber only offers Y" -->

**Proposed solution**

<!-- API sketch, expected sizes, classes, error messages, anything that shows the shape of the change. -->

**Which of pykyber's promises does this touch?**

<!-- for crypto/API changes: current behavior to preserve (legacy Kyber byte-compatibility), constant-time requirements, dependency policy (no new runtime crypto deps), Python version floor (3.8) -->

**Anything else?**
34 changes: 34 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
One work package (or smaller) per PR, opened against `dev-v2` from a
`wp<N>-*` branch. Title in Conventional Commits style
(feat:|fix:|test:|ci:|docs:|security:|refactor:). Fold review fixes into the
commit that introduced the problem (--fixup + autosquash); no "fix review"
commits. Mark checklist items n.a. where they don't apply and say why.
-->

## What does this PR do?

## How was it tested?

<!--
Record what you ran and the results. Every commit must pass on its own:

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test --no-fail-fast
maturin develop && pytest python/tests
git rebase --exec '<the same chain>' dev-v2
-->

## Checklist (from AGENTS.md)

- [ ] Spec sections cited in code for new crypto logic
- [ ] Legacy golden vectors pass unchanged
- [ ] No secret-dependent branches, divisions, or indexing (asm checked: yes/no/n.a.)
- [ ] Secret intermediates zeroized on all paths
- [ ] Inputs validated before secret-dependent work
- [ ] No new runtime deps; no unsafe; no panics on untrusted input
- [ ] Tests added (vectors for crypto); fmt/clippy/test/pytest green on every commit (`git rebase --exec`)
- [ ] Review fixes folded into the commits they fix (no "fix review" commits); rebased on current `dev-v2`
- [ ] CHANGELOG updated (if user-visible)
- [ ] Sources checked: <url>, <date>, <result>
54 changes: 53 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ on:
push:
branches:
- main
# Manual edit, WP-0 / issue #4: 2.0 development happens on dev-v2, so
# CI must also run on pushes there (PRs already run on pull_request).
# `maturin generate-ci github` will not emit this line - re-add it if
# the file is ever regenerated.
- dev-v2
tags:
- '*'
pull_request:
Expand All @@ -18,6 +23,53 @@ permissions:
contents: read

jobs:
# Added by hand for issue #7 (maturin generate-ci does not emit it; re-add it
# if the file is regenerated). Making `test` and `commits` required checks on
# dev-v2 is a branch-protection setting that only the maintainer can change.
test:
name: test (${{ matrix.os }}, py${{ matrix.python }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.8', '3.x']
exclude:
# setup-python has no Python 3.8 build for Apple silicon.
- os: macos-latest
python: '3.8'
include:
- os: macos-15-intel
python: '3.8'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
- name: Install Rust toolchain
run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy --no-self-update
- name: Create virtualenv
shell: bash
run: |
python -m venv "$RUNNER_TEMP/venv"
if [ -d "$RUNNER_TEMP/venv/Scripts" ]; then BIN=Scripts; else BIN=bin; fi
echo "VIRTUAL_ENV=$RUNNER_TEMP/venv" >> "$GITHUB_ENV"
echo "$RUNNER_TEMP/venv/$BIN" >> "$GITHUB_PATH"
- name: Install dev dependencies
run: python -m pip install -r requirements-dev.txt
- name: Fetch external test vectors
run: python scripts/fetch_vectors.py
- name: cargo fmt
run: cargo fmt --check
- name: cargo clippy
run: cargo clippy --all-targets -- -D warnings
- name: cargo test
run: cargo test --locked --no-fail-fast
- name: maturin develop
run: maturin develop
- name: pytest
run: python -m pytest python/tests

linux:
runs-on: ${{ matrix.platform.runner }}
strategy:
Expand Down Expand Up @@ -295,7 +347,7 @@ jobs:
name: Release
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
needs: [linux, linux-pypy, musllinux, windows, macos, android, sdist]
needs: [test, linux, linux-pypy, musllinux, windows, macos, android, sdist]
permissions:
# Use to sign the release artifacts
id-token: write
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ __pycache__/

# Opencode
.opencode/

# External test vectors, fetched by scripts/fetch_vectors.py
tests/vectors/external/
137 changes: 137 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# AGENTS.md — instructions for AI coding agents

You are working on **pykyber**, a post-quantum KEM library: an in-house Rust implementation (no third-party crypto at runtime) with PyO3 bindings, released under MIT OR Apache-2.0 (version 1.0.5 was released under the Unlicense). People will run your code to protect real secrets. A test that passes is necessary but not sufficient; code that is correct but leaks timing is a vulnerability.

Current work: the 2.0 release (GitHub issue #3), split into work packages (WPs), the sub-issues of #3. Read the WP you are assigned before you start.

## Non-negotiables

1. **Legacy Kyber output never changes.** `Kyber512/768/1024` must produce byte-identical results to v1.0.5. The golden vectors in `tests/vectors/legacy-kyber-v1.0.5.json` are append-only: never regenerate or edit them to make a test pass. If a change breaks them, the change is wrong.
2. **Follow the spec, not memory.** ML-KEM code implements FIPS 203 (final). Before writing or reviewing an algorithm, read its text in the standard and cite it in a comment (`// FIPS 203 Alg 18, step 9`). Round-3 Kyber and ML-KEM differ in ways that look like typos (`G(d‖k)`, no `H(m)`, no `H(c)` in the KDF); don't "fix" one into the other.
3. **Never weaken a test to make it pass.** Don't loosen an assertion, regenerate expected values from the code under test, or mark a test `#[ignore]`/`skip`. If you think a test is wrong, show evidence from a primary source and say so.
4. **No new runtime dependencies** without maintainer approval. Dev-only crates for testing (e.g. a reference ML-KEM for differential tests, `cargo-fuzz`) are fine in `[dev-dependencies]`.
5. **Don't touch the public API** beyond what the WP asks. No renames, no removals, no behavior changes to existing classes (other than the WP-6 deprecation warning).
6. **Don't claim what you didn't verify.** In PR descriptions and reports, separate "verified (how)" from "believed". If you couldn't run something, say so.

## Constant-time and secret-handling rules (Rust)

These apply to any value derived from a secret key, seed, `m`, `r`, noise, a decrypted message, or the implicit-rejection flag.

- No `if`, `match`, `&&`/`||` short-circuit, `?`, early `return`, or loop bound that depends on secret data.
- No `/` or `%` on secret values; use the existing multiply-and-shift (Barrett/Montgomery) helpers. Division by public constants on public lengths is fine.
- No array indexing with secret-derived indices.
- No `==` on secret byte slices. Use the constant-time compare/select in `verify.rs` (or its ML-KEM successor), which must sit behind an optimization barrier.
- Mask idioms like `x.wrapping_neg() & y` are **not** guaranteed constant-time: LLVM has turned them into branches (CVE-2024-37880, "Clangover"). Route the bit through a barrier, and check the assembly when in doubt.
- Zeroize every secret intermediate before the function returns (`zeroize::Zeroizing`, or `.zeroize()` on every exit path). FIPS 203 §3.3 requires it, and the decaps reject flag must never be returned or stored.
- Validate inputs (lengths, the §7.2 modulus check, the §7.3 hash check) **before** any secret-dependent work, and return typed errors.
- Randomness for keys and encapsulation comes from the OS RNG; propagate failure as an error and never fall back to a weaker source.
- No `unsafe`. No `unwrap`/`expect`/`panic!`/indexing that can panic on attacker-controlled input outside tests. Panics must not cross the PyO3 boundary.
- No floating point anywhere in crypto code (FIPS 203 §3.3).
- `*_internal` functions that take caller-chosen randomness are test-only: `pub(crate)`, exposed only under `cfg(any(test, feature = "acvp"))`.

## Python rules

- Public classes mirror the existing `Kyber*` shape (instance = keypair, static `encapsulate`/`decapsulate`/`from_keys`, hex/b64/dict, tuple unpacking). Keep `__all__` and the `.pyi` stubs in sync.
- Validate sizes in `_validation.py` before calling Rust; raise `KyberError` (or a subclass), never a bare `Exception`.
- Kyber and ML-KEM keys have identical sizes. Never accept an object from one family in the other's methods.

## Constants, not magic numbers

Applies to all new code, Rust and Python, tests included.

- Give every number a name unless its meaning is obvious on the line. `0`, `1`, `x / 2`, and a loop over the 8 bits of a byte are fine. A bit width, a size, a modulus, a shift, a multiplier, an offset, a count of rounds or inputs, or a residue you single out is not: it gets a `const` with a doc comment saying what it is and where it comes from (a FIPS 203 table or algorithm line, or the derivation, such as "ceil(2^31 / q)").
- Derive instead of retyping. Build sizes from the constants that already exist (`KYBER_N`, `KYBER_Q`, `KYBER_POLYBYTES`, `KYBER_SYMBYTES`) and from type widths (`u8::BITS`, `i16::BITS - 1`), so a size such as 1568 or a shift of 15 is never written out. If the same literal appears twice, it needs a name.
- Prefer a loop or accumulator driven by a named width over hand-unrolled shifts with a different literal on every line.
- Exceptions: a function or test that transcribes a spec algorithm, formula or table line by line may keep the standard's own literals if a comment names the algorithm and lines. Expected values in test vectors, PRNG seeds, and the values in the parameter tables themselves (one match arm per set, commented with the table) are data, not magic numbers.
- Legacy code under `src/kyber/` mirrors the reference C and is frozen. Do not tidy its literals as a drive-by; that would be its own refactor, checked against the golden vectors.

## Workflow

- Branch from `dev-v2` as `wp<N>-<short-name>`, and open every PR against `dev-v2`. Never target `main` or `dev`: the maintainer merges `dev-v2` into `main` once, when 2.0 is done.
- One WP (or less) per PR; Conventional Commit messages (`feat:`, `fix:`, `test:`, `ci:`, `docs:`, `security:`, `refactor:`). Never push directly to `dev-v2`, `main` or `dev`, never merge, never force-push shared branches.
- **Every commit must pass on its own.** PRs are rebase-merged, so each commit lands on `dev-v2` as is. Each commit must build and pass the checks below, not just the last one in the PR.
- **Fix code in the commit that introduced it.** When review or CI finds a problem, don't add a "fix review comments" commit. Make a fixup commit and fold it in:
```bash
git commit --fixup=<sha-of-commit-being-fixed>
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash dev-v2
git push --force-with-lease # only ever on your own wp<N>-* branch
```
Rebase onto the latest `dev-v2` the same way instead of merging `dev-v2` into your branch.
- Keep refactors separate from behavior changes. Crypto PRs should stay under about 400 changed lines, not counting vector files.
- Match the surrounding code style. New ML-KEM code is generic over the parameter set; don't add more `_512/_768/_1024` copies.
- Add a line under `## Unreleased` in `CHANGELOG.md` for user-visible changes. Version numbers live in **both** `Cargo.toml` and `pyproject.toml`.

### PR descriptions written by agents

- The description goes to the maintainer and reviewers as prose, not as a template shape. Keep the facts (commit list, verification commands and outcomes, sources checked) and state them plainly; read the draft once as a reviewer would and rewrite anything that reads like generated text: bold decoration on every item, dashes chaining every clause, staged close-outs like "bottom line is", fact-free emphasis.
- Post only character-safe text. Write the body or comment to a file first and byte-check that it is 7-bit ASCII (`grep -nP '[^\x00-\x7F]' <file>` must report no matches) before creating or editing it; non-ASCII text has already reached GitHub as mojibake in this repo. Use ASCII hyphens and straight quotes; tidy the file until the check is clean.
- Fetch and update PR/issue text through the REST API when the `gh` commands themselves fail on GraphQL errors (here, `gh issue view` and `gh pr edit` die on the "Projects (classic)" deprecation): for example `gh api -X PATCH repos/Zikithezikit/pykyber/pulls/<number> -F body=@<file>`, then read the body back with `gh api ... --jq .body` and run the ASCII check again. What GitHub stores is what reviewers see.

### Before saying you're done

Run all of these and report the results:

```bash
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test --no-fail-fast
maturin develop && pytest python/tests
```

Then prove every commit in the branch passes, not only the tip:

```bash
git rebase --exec 'cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test --no-fail-fast && maturin develop && pytest python/tests' dev-v2
```

If you touched secret-handling code, also complete the PR checklist below and include evidence (e.g. the assembly grep output) in the PR.

## Online sources

Standards and advisories change. At the start of each WP, and whenever you rely on a spec detail or a vulnerability claim:

1. Check the primary sources:
- FIPS 203 (final) and errata: <https://csrc.nist.gov/pubs/fips/203/final>
- SP 800-227 (KEM recommendations): <https://csrc.nist.gov/pubs/sp/800/227/final>
- ACVP ML-KEM spec and vectors: <https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html>, <https://github.com/usnistgov/ACVP-Server>
- C2SP CCTV ML-KEM: <https://c2sp.org/CCTV/ML-KEM>; Wycheproof: <https://github.com/C2SP/wycheproof>
- Advisories: NVD <https://nvd.nist.gov/vuln/search>, GitHub Advisory DB <https://github.com/advisories>, RustSec <https://rustsec.org>
- NIST pqc-forum: <https://groups.google.com/a/list.nist.gov/g/pqc-forum>
2. Record in the PR: source URL, date checked, and what you found (including "no change").
3. Treat blogs, news, and search-engine summaries as leads. Confirm on the primary source before acting on them, and label anything unconfirmed as such.
4. External test vectors are never committed. They're downloaded at test time from a pinned commit and checked against `tests/vectors/manifest.json` (SHA-256). Don't silently change a pin or a hash.
5. Other implementations (Go `crypto/mlkem`, BoringSSL, RustCrypto `ml-kem`, libcrux) are for reading and for comparing outputs in dev-only tests. Never copy their code into this repo, not even with edits. Write ours from the FIPS 203 text. If you think copying is needed, stop and ask.

## Security-review agents

When asked to audit code (WP-8/WP-9), you are a reviewer, not an implementer: don't change code in the same task unless told to. For each issue, report:

```
ID: SEC-<NNN>
Title:
Severity: Critical / High / Medium / Low / Info (with one-line rationale)
Location: path:line (function)
Class: timing | memory-hygiene | input-validation | rng | panic/DoS | api-misuse | spec-deviation | supply-chain
Evidence: what you observed (code, asm snippet, test output). Mark it "hypothesis" if not demonstrated.
Related: CVE / paper / spec section, with link
Legacy impact: does a fix change Kyber v1.0.5 output? (must be "no", or the fix needs another approach)
Suggested fix + test that would catch a regression
```

Check at least: everything in the constant-time rules above; KyberSlash (division by q) and Clangover (mask-to-branch) patterns in `poly`, `polyvec` and `verify`; implicit-rejection selection; input checks; zeroization on every exit path including errors; RNG source and error handling; reachability of panics from Python; and key/algorithm confusion in the Python API (cf. 2026 CVEs about binding a key to the wrong pair). Report "no findings" per area explicitly instead of omitting it.

## PR checklist (copy into the PR description)

```
- [ ] Spec sections cited in code for new crypto logic
- [ ] Legacy golden vectors pass unchanged
- [ ] No secret-dependent branches, divisions, or indexing (asm checked: yes/no/n.a.)
- [ ] Secret intermediates zeroized on all paths
- [ ] Inputs validated before secret-dependent work
- [ ] No new runtime deps; no unsafe; no panics on untrusted input
- [ ] No magic numbers: sizes, widths, multipliers and counts are named, documented constants
- [ ] Tests added (vectors for crypto); fmt/clippy/test/pytest green on every commit (`git rebase --exec`)
- [ ] Review fixes folded into the commits they fix (no "fix review" commits); rebased on current `dev-v2`
- [ ] CHANGELOG updated (if user-visible)
- [ ] Sources checked: <url> — <date> — <result>
```
Loading
Loading