Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 0 additions & 35 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,41 +70,6 @@ jobs:
- name: pytest
run: python -m pytest python/tests

# PRs are rebase-merged, so every commit lands on dev-v2 as is. Re-run the
# checks on each commit of the PR, not only the tip.
commits:
name: every commit passes
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install Rust toolchain
run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy --no-self-update
- name: Create virtualenv
run: |
python -m venv "$RUNNER_TEMP/venv"
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: Run checks on every commit
env:
BASE_REF: ${{ github.base_ref }}
run: |
git config user.name "ci"
git config user.email "ci@users.noreply.github.com"
git fetch origin "$BASE_REF"
# Copy the script out of the tree: commits older than the one that
# adds scripts/check.sh must still be checked with it.
cp scripts/check.sh "$RUNNER_TEMP/check.sh"
git rebase --exec "bash $RUNNER_TEMP/check.sh" "origin/$BASE_REF"

linux:
runs-on: ${{ matrix.platform.runner }}
strategy:
Expand Down
12 changes: 9 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 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 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.
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), planned in `PLAN-2.0.md`. Read the work package (WP) you are assigned before you start.
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

Expand Down Expand Up @@ -90,7 +90,13 @@ If you touched secret-handling code, also complete the PR checklist below and in

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 listed in `PLAN-2.0.md` §9 (FIPS 203, SP 800-227, ACVP, C2SP/Wycheproof, NVD/GHSA/RustSec, pqc-forum).
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.
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

- License: PyKyber is now licensed under MIT OR Apache-2.0, at the user's option (`LICENSE-MIT`, `LICENSE-APACHE`; `license = "MIT OR Apache-2.0"` in `Cargo.toml` and `pyproject.toml`). Version 1.0.5 was released under the Unlicense, and it still is. The `LICENSE` file and the "License :: Public Domain" classifier are gone.

- CI: removed the `every commit passes` job. The `test` job still runs on every PR and push.
- Housekeeping: removed `PLAN-2.0.md` (the work packages are the sub-issues of #3; the primary-source list moved to `AGENTS.md`), `opencode.json` and the comment-only `requirements.txt`.

- Docs: the README is now only install, quick start, sizes, performance and the Kyber deprecation note; the rest moved to `docs/`, and the new `docs/enterprise.md` covers licensing, dependencies, supply chain, security status and support for companies. Earlier: the README is shorter and rewritten in plain prose. The benchmark (`performance-tests/benchmark.py`) now times `ML_KEM_*` as well as the Kyber classes on a release build, writes `results.json`, and draws two charts (`latency.png`, `ml_kem_vs_kyber.png`) that replace the four old ones. The old README figures were for the Kyber classes only. `make benchmark` builds the release extension first, and the chart dependency is in `performance-tests/requirements.txt`.

- Security pass over the legacy Kyber code (issue #18): the random bytes for `Kyber*` key generation and encapsulation now come from the operating system generator (`OsRng`) instead of `thread_rng()`, and a failure of that generator is raised as `RandomnessError` (a `KyberError`) instead of panicking or being retried with a weaker source. Seeded paths and all Kyber output are unchanged.
- Security pass over the legacy Kyber code (issue #18), continued: optimization barriers in `poly_frommsg`, `verify` and `cmov`; decapsulation and the PRF noise buffers are wiped on every exit; secrets returned to Python are wiped on the Rust side; the Rust keypair and encapsulation functions check buffer lengths; `rand` is 0.8.6 in `Cargo.lock`. The findings, the CVE and paper table and the two decisions not to fix (Compress_11, Keccak stack spills) are in `SECURITY.md`. Kyber output is unchanged.
- Behavior change, `Kyber512/768/1024.from_keys` (issue #18): it now raises `KyberError` when the public key is not the copy stored inside the secret key (a round-3 Kyber secret key is `sk_pke || pk || H(pk) || z`). Before, any public key and secret key of the right sizes were accepted, so `Kyber768.from_keys(a.public_key, b.secret_key)` built a keypair whose `decapsulate` silently returned a different secret than `encapsulate` had produced. A key pair that matches is accepted as before, and a wrong-size key keeps its existing size error. `ML_KEM_*.from_keys` is not changed.
Expand All @@ -12,7 +19,7 @@ Planned release; not tagged or published yet. Adds ML-KEM (FIPS 203) beside the

- 2.0 development: repo setup (issue #4), including CI on pushes to `dev-v2`, new `CONTRIBUTING.md`, root `SECURITY.md` (vulnerability-reporting policy + migrated security findings log), PR and issue templates, `CODEOWNERS` routing `src/` and `SECURITY.md` to the maintainer for review, and PR-description rules for agents in AGENTS.md (plain prose, ASCII-checked post text, REST fallback).
- Legacy freeze (issue #6): golden vectors capture the byte output of pykyber v1.0.5 (append-only `tests/vectors/legacy-kyber-v1.0.5.json`, with the generator); Rust and Python tests fail on any change to seeded keygen, seeded encapsulation, or decapsulation output.
- CI (issue #7): a `test` job on Linux, macOS and Windows (oldest and newest Python) runs `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test --locked`, `maturin develop` and `pytest`; a `commits` job re-runs them on every commit of a PR via `git rebase --exec scripts/check.sh`. `requirements-dev.txt` declares maturin and pytest; wheel release now waits for `test`.
- CI (issue #7): a `test` job on Linux, macOS and Windows (oldest and newest Python) runs `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test --locked`, `maturin develop` and `pytest`. (A per-commit `every commit passes` job was added here and later removed; `scripts/check.sh` can still be run per commit with `git rebase --exec`.) `requirements-dev.txt` declares maturin and pytest; wheel release now waits for `test`.
- ML-KEM (issue #13): new classes `ML_KEM_512`, `ML_KEM_768` and `ML_KEM_1024` (FIPS 203) with the same shape as `Kyber*` (an instance is a keypair; static `encapsulate`, `decapsulate`, `from_keys`; hex/b64/dict; tuple unpacking). A 64-byte seed `d || z` gives deterministic keys and is exposed as `.seed` only for such keypairs. ML-KEM methods refuse `Kyber*` objects by type (`AlgorithmMismatchError`); raw bytes cannot be told apart. New `KyberError` subclasses: `InvalidInputError`, `InvalidEncapsulationKeyError`, `InvalidDecapsulationKeyError`, `RandomnessError`, `DecapsulationError`, `AlgorithmMismatchError`. The Kyber classes are unchanged.
- Kyber deprecation (issue #14): the first use of any `Kyber512/768/1024` entry point (constructor, `from_keys`, static `encapsulate` or `decapsulate`) emits one `KyberDeprecationWarning` (a `DeprecationWarning` subclass, exported from `pykyber`) per process. Importing does not warn, `ML_KEM_*` never warns, and Kyber output is unchanged. Python shows `DeprecationWarning` by default only when it is attributed to `__main__`; run with `-W default::DeprecationWarning` or call `warnings.simplefilter` to see it elsewhere.
- Docs (issue #15): the README quick start, `docs/api.md` and `docs/index.md` use `ML_KEM_768` (NIST's default parameter set, FIPS 203 section 8), the Kyber classes are documented as deprecated, and the new `docs/migration.md` explains the move from Kyber to ML-KEM. The version is 2.0.0 in `Cargo.toml` and `pyproject.toml`.
Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## What this is

PyKyber: a Python package (`pykyber`) exposing round-3 CRYSTALS-Kyber, implemented from scratch in Rust (its own Keccak/FIPS 202, NTT, etc.; no external crypto crate) and bound with PyO3 + maturin (abi3, Python ≥3.8). Unlicense. Current version 1.0.5; the version lives in both `Cargo.toml` and `pyproject.toml`, so keep them in sync.
PyKyber: a Python package (`pykyber`) exposing round-3 CRYSTALS-Kyber, implemented from scratch in Rust (its own Keccak/FIPS 202, NTT, etc.; no external crypto crate) and bound with PyO3 + maturin (abi3, Python ≥3.8). MIT OR Apache-2.0 from 2.0 (version 1.0.5 was released under the Unlicense). Current version 1.0.5; the version lives in both `Cargo.toml` and `pyproject.toml`, so keep them in sync.

Active work is 2.0 (issue #3): add ML-KEM (FIPS 203) beside the legacy Kyber classes. The plan, the work packages and the verified spec facts are in `PLAN-2.0.md`.
Active work is 2.0 (issue #3): add ML-KEM (FIPS 203) beside the legacy Kyber classes. The work packages are the sub-issues of #3.

## Commands

Expand All @@ -29,7 +29,7 @@ pytest python/tests/test_kyber.py::test_kyber768_new_api

`make clean` deletes `target/`, the built `.so`, and the `.pyi`; rebuild afterwards.

Known baseline (2026-10-30, after WP-4 / #12 and WP-5 / #13 merged): `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` and `pytest python/tests` all pass on `dev-v2`, and the legacy golden vectors (#6) are in. CI (`.github/workflows/CI.yml`) has a `test` job (Linux/macOS/Windows, Python 3.8 and newest) and an `every commit passes` job that runs `scripts/check.sh` over each commit of a PR via `git rebase --exec`; making them required checks on `dev-v2` is a branch-protection setting only the maintainer can change (#7). The clippy lints `needless_range_loop`, `identity_op` and `explicit_counter_loop` are allowed per module in `src/kyber/` because the legacy loops mirror the reference C code; don't add new code that needs them. CI installs the latest stable toolchain, which can be newer than the one on your machine and bring new clippy lints (rustc 1.99 added `chunks_exact_to_as_chunks`, which failed PR #28 although 1.95 was clean), so run clippy with a recent stable before pushing: `rustup toolchain install stable` and `cargo +stable clippy --all-targets -- -D warnings`.
Known baseline (2026-10-30, after WP-4 / #12 and WP-5 / #13 merged): `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test` and `pytest python/tests` all pass on `dev-v2`, and the legacy golden vectors (#6) are in. CI (`.github/workflows/CI.yml`) has a `test` job (Linux/macOS/Windows, Python 3.8 and newest); making it a required check on `dev-v2` is a branch-protection setting only the maintainer can change (#7). The clippy lints `needless_range_loop`, `identity_op` and `explicit_counter_loop` are allowed per module in `src/kyber/` because the legacy loops mirror the reference C code; don't add new code that needs them. CI installs the latest stable toolchain, which can be newer than the one on your machine and bring new clippy lints (rustc 1.99 added `chunks_exact_to_as_chunks`, which failed PR #28 although 1.95 was clean), so run clippy with a recent stable before pushing: `rustup toolchain install stable` and `cargo +stable clippy --all-targets -- -D warnings`.

## Architecture

Expand All @@ -49,7 +49,7 @@ Official vectors (WP-4, merged): the manifest pins ACVP, C2SP CCTV and Wycheproo

Other locations: docs are in `docs/` (`api.md`, `index.md`), the benchmark is in `performance-tests/`, and `CHANGELOG.md` is maintained by hand.

`.opencode/` (another agent tool's notes, including a `SECURITY.md` log of past fixes: constant-time division removal, Kyber-512 η1=3) is **gitignored**, so other contributors don't have it. PLAN-2.0 WP-0 migrates that log to a root `SECURITY.md`.
`.opencode/` (another agent tool's notes, including a `SECURITY.md` log of past fixes: constant-time division removal, Kyber-512 η1=3) is **gitignored**, so other contributors don't have it. That log was migrated to the root `SECURITY.md`.

## Repo state

Expand Down
16 changes: 11 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ PyKyber is a post-quantum KEM library: round-3 CRYSTALS-Kyber, implemented
from scratch in Rust (no third-party crypto at runtime) and exposed to Python
through PyO3. A 2.0 release adds ML-KEM (FIPS 203) beside the legacy Kyber
API. This file covers how to work on the repo; `AGENTS.md` is the binding
rulebook for humans *and* AI agents, and `PLAN-2.0.md` tracks the 2.0 work
packages.
rulebook for humans *and* AI agents. The 2.0 work packages are the
sub-issues of #3.

## Development setup

Expand Down Expand Up @@ -39,16 +39,15 @@ These commands are the gate for every PR:
scripts/check.sh # fmt, clippy -D warnings, cargo test, maturin develop, pytest
```

CI runs the same checks on Linux, macOS and Windows, and again on every commit of
a PR (`git rebase --exec scripts/check.sh origin/dev-v2`).
CI runs the same checks on Linux, macOS and Windows.

## Branching and PRs

All 2.0 development happens on **`dev-v2`**. The default branch is still
`dev` until the 2.0 release; `main` is the stable release branch. During 2.0:

- Branch from `dev-v2` as `wp<N>-<short-name>` (e.g. `wp3-mlkem-keygen`),
where `N` is the work-package number from `PLAN-2.0.md` / the linked sub-issue of #3.
where `N` is the work-package number of the linked sub-issue of #3.
- Open every PR **against `dev-v2`**. Never target `main` or `dev`.
- One work package (or less) per PR. Crypto PRs should stay under ~400
changed lines excluding vector files.
Expand Down Expand Up @@ -122,6 +121,13 @@ The full rules are in `AGENTS.md`; the ones most often violated:
for reading and output comparison in dev-only tests. Their code is never
copied into this repo.

## Licensing

PyKyber is licensed under MIT OR Apache-2.0, at the user's option. Unless you
say otherwise, a contribution you submit is licensed the same way, without extra
terms. Do not paste in code under another license, and never copy code from the
other implementations listed above.

## Reporting security issues

Do not open a public issue for security problems. Use GitHub's private
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "pykyber"
version = "2.0.0"
edition = "2021"
license = "MIT OR Apache-2.0"

[lib]
name = "pykyber"
Expand Down
24 changes: 0 additions & 24 deletions LICENSE

This file was deleted.

Loading
Loading