Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
abd3b97
docs: add 2.0 plan and agent instructions
Zikithezikit Oct 29, 2026
963d932
ci: run CI on pushes to dev-v2
Zikithezikit Oct 29, 2026
a7fbf88
docs: add CONTRIBUTING.md
Zikithezikit Oct 29, 2026
a2ed968
ci: add pull request template and CODEOWNERS
Zikithezikit Oct 29, 2026
526a3f9
ci: add issue templates (bug, feature, security redirect)
Zikithezikit Oct 29, 2026
7487640
security: add SECURITY.md with reporting policy and findings log
Zikithezikit Oct 29, 2026
95c5736
docs: note 2.0.0 repo setup in changelog
Zikithezikit Oct 29, 2026
f33229a
docs: add PR-description rules for agents to AGENTS.md
Zikithezikit Oct 29, 2026
6e29bef
test: fix Kyber-1024 compression byte sizes per Kyber round-3 spec
Zikithezikit Oct 29, 2026
4d85c27
test: capture legacy Kyber golden vectors from v1.0.5
Zikithezikit Oct 29, 2026
c25560d
test: check current Kyber output against v1.0.5 golden vectors
Zikithezikit Oct 29, 2026
9f4a95c
Revert "test: check current Kyber output against v1.0.5 golden vectors"
Zikithezikit Oct 30, 2026
2328ac5
Revert "test: capture legacy Kyber golden vectors from v1.0.5"
Zikithezikit Oct 30, 2026
7434f3d
test: capture legacy Kyber golden vectors from v1.0.5
Zikithezikit Oct 29, 2026
2716829
test: check current Kyber output against v1.0.5 golden vectors
Zikithezikit Oct 29, 2026
bede75a
docs: refresh CLAUDE.md test baseline after legacy vector freeze
Zikithezikit Oct 30, 2026
6351806
refactor: make cargo clippy --all-targets -D warnings pass
Zikithezikit Oct 30, 2026
a7a1c4f
build: declare maturin and pytest as dev dependencies, add scripts/ch…
Zikithezikit Oct 30, 2026
c6a3186
ci: run tests and lints on every PR and on every commit of a PR
Zikithezikit Oct 30, 2026
97f1a88
test: check the primitives ML-KEM would share against FIPS 202/203
Zikithezikit Oct 30, 2026
d577aca
security: zeroize the Keccak state in sha3_256 and sha3_512
Zikithezikit Oct 30, 2026
137849b
feat: add ML-KEM parameter type and PolyVec generic over k
Zikithezikit Oct 30, 2026
ba781f4
feat: add SampleNTT on top of the legacy rej_uniform
Zikithezikit Oct 30, 2026
7be6671
feat: add an exact Compress_11 for ML-KEM-1024
Zikithezikit Oct 30, 2026
a0c2a73
docs: record which legacy primitives ML-KEM shares and which it does not
Zikithezikit Oct 30, 2026
751a05d
docs: refresh CLAUDE.md baseline after the clippy cleanup and CI test…
Zikithezikit Oct 30, 2026
a1fdd40
docs: note that CI clippy runs on the latest stable toolchain
Zikithezikit Oct 30, 2026
984cd62
docs: ask agents to use named constants instead of magic numbers
Zikithezikit Oct 30, 2026
73b24ea
refactor: make the exact Compress_d generic over d
Zikithezikit Oct 30, 2026
1577db8
feat: add Decompress_d and a branch-free message decoder for ML-KEM
Zikithezikit Oct 30, 2026
6cfb0d8
feat: add K-PKE KeyGen, Encrypt and Decrypt for ML-KEM
Zikithezikit Oct 30, 2026
03d62fb
test: add a fetch script for pinned external test vectors
Zikithezikit Oct 30, 2026
14701c9
test: check K-PKE against the NIST ACVP ML-KEM vectors
Zikithezikit Oct 30, 2026
2859fbd
feat: add error variants for failed ML-KEM key checks
Zikithezikit Oct 30, 2026
4366d6b
feat: add input checks and a constant-time reject select for ML-KEM
Zikithezikit Oct 30, 2026
8eed257
feat: add ML-KEM KeyGen, Encaps and Decaps (FIPS 203 Algorithms 16-21)
Zikithezikit Oct 30, 2026
dce9d36
test: run the real Decaps_internal and the key checks against ACVP
Zikithezikit Oct 30, 2026
3f3aa86
refactor: build three ML-KEM size constants only for the tests that r…
Zikithezikit Oct 30, 2026
f495537
feat: expose ML-KEM keygen, encaps and decaps to Python as private fu…
Zikithezikit Oct 30, 2026
c406cad
feat: add ML_KEM_512, ML_KEM_768 and ML_KEM_1024 Python classes
Zikithezikit Oct 30, 2026
6e72926
test: run the NIST ACVP ML-KEM vectors through the Python API
Zikithezikit Oct 30, 2026
0fb61ad
docs: describe the ML-KEM layers in CLAUDE.md
Zikithezikit Oct 30, 2026
4c2e543
test: run the ACVP ML-KEM FIPS203-tr1 encapDecap vectors
Zikithezikit Oct 30, 2026
204fd4d
refactor: split K-PKE.KeyGen at the expansion of rho and sigma
Zikithezikit Oct 30, 2026
32ab9e8
test: run the C2SP CCTV ML-KEM vectors in Rust
Zikithezikit Oct 30, 2026
2945620
test: run the C2SP CCTV ML-KEM vectors through the Python API
Zikithezikit Oct 30, 2026
a637b63
test: run the C2SP Wycheproof ML-KEM vectors in Rust
Zikithezikit Oct 30, 2026
7ed491b
test: run the C2SP Wycheproof ML-KEM vectors through the Python API
Zikithezikit Oct 30, 2026
0bf6007
test: compare ML-KEM with RustCrypto ml-kem on random inputs
Zikithezikit Oct 30, 2026
0f42b0d
docs: record the WP-4 vector setup in CLAUDE.md
Zikithezikit Oct 30, 2026
a666d76
feat: warn once per process on first use of a Kyber class
Zikithezikit Oct 30, 2026
6883dd8
test: count Kyber deprecation warnings in subprocesses
Zikithezikit Oct 30, 2026
8e94eb7
docs: add the Kyber to ML-KEM migration guide
Zikithezikit Oct 30, 2026
9b3750a
docs: use ML-KEM in the README and API reference, mark Kyber deprecated
Zikithezikit Oct 30, 2026
8e56f40
chore: bump the version to 2.0.0 and add the 2.0.0 changelog entry
Zikithezikit Oct 30, 2026
17da223
security: put an optimization barrier around the poly_frommsg mask
Zikithezikit Oct 30, 2026
c995354
security: put an optimization barrier in legacy verify and cmov
Zikithezikit Oct 30, 2026
ea7d76c
security: take legacy Kyber randomness from OsRng and fail closed
Zikithezikit Oct 30, 2026
b409be1
security: wipe every secret intermediate of legacy decapsulation
Zikithezikit Oct 30, 2026
03f6172
fix: make Kyber from_keys reject a public key that is not the one in …
Zikithezikit Oct 30, 2026
3080ee4
security: wipe the Rust copies of secrets returned to Python
Zikithezikit Oct 30, 2026
18df4e4
fix: return an error from the unused DummyRng instead of panicking
Zikithezikit Oct 30, 2026
5dabe67
fix: check buffer and seed lengths in the legacy keypair and encapsul…
Zikithezikit Oct 30, 2026
c7193d8
chore: update rand to 0.8.6 for RUSTSEC-2026-0097
Zikithezikit Oct 30, 2026
da445e4
docs: log the legacy Kyber security pass and the CVE review in SECURI…
Zikithezikit Oct 30, 2026
85f7373
docs: benchmark ML-KEM on a release build and redraw the charts
Zikithezikit Oct 31, 2026
51d5462
docs: shorten the README and rewrite it in plain prose
Zikithezikit Oct 31, 2026
7eea1aa
chore: license under MIT OR Apache-2.0 instead of the Unlicense
Zikithezikit Oct 31, 2026
03f019d
docs: reduce the README to the essentials and add a guide for companies
Zikithezikit Oct 31, 2026
64d2c62
docs: use the same Unlicense wording for 1.0.5 everywhere
Zikithezikit Oct 31, 2026
5e2c3a1
chore: remove the 2.0 plan, the opencode config and an empty requirem…
Zikithezikit Oct 31, 2026
8afb6b2
ci: remove the every-commit-passes job
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