Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
31affff
sha2: partial-bit messages, compile-time IVs, CAVP SHAVS tests (PR #88)
dghgit Sep 6, 2026
aec1ae4
sha3: partial-byte fixes, CAVP SHA3VS tests, mem-usage bench, release…
dghgit Sep 6, 2026
d604906
sha2, hmac: add SHA-512/224 and SHA-512/256 (FIPS 180-4 s. 5.3.6) and…
dghgit Sep 6, 2026
a57d508
rng: use core::fmt in hash_drbg80090a.rs; the only part of PRs #92-#9…
dghgit Sep 6, 2026
fa0be5d
sm3: add bouncycastle-sm3 (GB/T 32905-2016) and HMAC-SM3 with factory…
dghgit Sep 6, 2026
34d7953
Partial bytes follow ASN.1 BIT STRING order (X.690 s. 8.6.2): message…
dghgit Sep 6, 2026
1140a61
release notes: SM3 partial bytes follow the ASN.1 BIT STRING order li…
dghgit Sep 6, 2026
1ad97fd
sha2, sm3: document the surviving cargo-mutants equivalences at their…
dghgit Sep 6, 2026
fe6fd58
core: split BlockCipher into block-aligned BlockCipherEncryptor/Decry…
dghgit Sep 6, 2026
58a1fed
aes-lowmemory: add bouncycastle-aes-lowmemory, a constant-time, table…
dghgit Sep 6, 2026
aa9454d
modes: add BlockPermutation trait, bouncycastle-modes with AES CBC, a…
dghgit Sep 6, 2026
f56802e
padding: add Padding trait and bouncycastle-padding (PKCS7, PaddedEnc…
dghgit Sep 6, 2026
78a4021
core, modes, aes-lowmemory: in-place block cipher API with compile-ti…
dghgit Sep 6, 2026
c5f60fb
modes: add AES CFB128 mode with AES_CFB_* aliases, aes*-cfb CLI subco…
dghgit Sep 6, 2026
2c0567e
core: ElectronicCodeBook (was BlockPermutation), slice block hooks, b…
dghgit Sep 6, 2026
157b1c8
modes: add Ecb (SP 800-38A Sec 6.1) with AES_ECB_* aliases and aes*-e…
dghgit Sep 6, 2026
f6cb787
padding: add NoPadding (errors when asked to pad) with Padding::ALWAY…
dghgit Sep 6, 2026
a1c4e41
skills: add commit-range-report, a Markdown report of a commit range …
dghgit Sep 6, 2026
9c65521
mldsa, mlkem: replace the const-generic turbofish with sealed MLDSAPa…
ounsworth Sep 7, 2026
57dd3d0
core: replace StreamCipher with the split StreamCipherEncryptor / Str…
dghgit Sep 6, 2026
97ac6e3
modes: Cfb becomes a stream cipher taking any length with no padding,…
dghgit Sep 6, 2026
5c73617
release notes: CFB becomes a stream cipher with a short final segment…
dghgit Sep 6, 2026
e2b534d
modes: pin the single-call vs chunked equivalence of Cfb and Cfb8 aga…
dghgit Sep 6, 2026
f72bfe6
modes: add Ctr (SP 800-38A Sec 6.5), a stream cipher whose nonce leng…
dghgit Sep 6, 2026
0404ab9
modes: cross-check Ctr against BC Java's SICBlockCipher, which shares…
dghgit Sep 6, 2026
921e2b5
sha2: partial-bit messages, compile-time IVs, CAVP SHAVS tests (PR #88)
dghgit Sep 6, 2026
7b4e7fc
sha3: partial-byte fixes, CAVP SHA3VS tests, mem-usage bench, release…
dghgit Sep 6, 2026
c34c2f9
sha2, hmac: add SHA-512/224 and SHA-512/256 (FIPS 180-4 s. 5.3.6) and…
dghgit Sep 6, 2026
7df74a6
rng: use core::fmt in hash_drbg80090a.rs; the only part of PRs #92-#9…
dghgit Sep 6, 2026
ac896e2
sm3: add bouncycastle-sm3 (GB/T 32905-2016) and HMAC-SM3 with factory…
dghgit Sep 6, 2026
f34858d
Partial bytes follow ASN.1 BIT STRING order (X.690 s. 8.6.2): message…
dghgit Sep 6, 2026
46e2e79
release notes: SM3 partial bytes follow the ASN.1 BIT STRING order li…
dghgit Sep 6, 2026
607cfa7
sha2, sm3: document the surviving cargo-mutants equivalences at their…
dghgit Sep 6, 2026
4ca1274
core: split BlockCipher into block-aligned BlockCipherEncryptor/Decry…
dghgit Sep 6, 2026
a9627f6
aes-lowmemory: add bouncycastle-aes-lowmemory, a constant-time, table…
dghgit Sep 6, 2026
f403921
modes: add BlockPermutation trait, bouncycastle-modes with AES CBC, a…
dghgit Sep 6, 2026
e018929
padding: add Padding trait and bouncycastle-padding (PKCS7, PaddedEnc…
dghgit Sep 6, 2026
17372c9
core, modes, aes-lowmemory: in-place block cipher API with compile-ti…
dghgit Sep 6, 2026
d1bee58
modes: add AES CFB128 mode with AES_CFB_* aliases, aes*-cfb CLI subco…
dghgit Sep 6, 2026
ca53601
core: ElectronicCodeBook (was BlockPermutation), slice block hooks, b…
dghgit Sep 6, 2026
891669b
modes: add Ecb (SP 800-38A Sec 6.1) with AES_ECB_* aliases and aes*-e…
dghgit Sep 6, 2026
45941d1
padding: add NoPadding (errors when asked to pad) with Padding::ALWAY…
dghgit Sep 6, 2026
74e0100
skills: add commit-range-report, a Markdown report of a commit range …
dghgit Sep 6, 2026
37d0b3b
core: replace StreamCipher with the split StreamCipherEncryptor / Str…
dghgit Sep 6, 2026
c158860
modes: Cfb becomes a stream cipher taking any length with no padding,…
dghgit Sep 6, 2026
8c7ec71
release notes: CFB becomes a stream cipher with a short final segment…
dghgit Sep 6, 2026
9c1b9b7
modes: pin the single-call vs chunked equivalence of Cfb and Cfb8 aga…
dghgit Sep 6, 2026
5cec55d
modes: add Ctr (SP 800-38A Sec 6.5), a stream cipher whose nonce leng…
dghgit Sep 6, 2026
4adbebb
modes: cross-check Ctr against BC Java's SICBlockCipher, which shares…
dghgit Sep 6, 2026
4bac3b3
Reverting the SKILL.md changes about producing a report since this se…
ounsworth Sep 9, 2026
fc2fcb7
Merge branch 'feature/stream-cipher' of git.bouncycastle.org:bc-rust …
ounsworth Sep 9, 2026
d66cd0b
Restructured the mem_usage_benchmarks sub-crate
ounsworth Sep 9, 2026
dc248f9
Reverting the SKILL.md changes about producing a report since this se…
ounsworth Sep 9, 2026
7539532
Moves sha512t_h0 tests out of unit tests and into integration tests. …
ounsworth Sep 9, 2026
b0bd491
rename sha2/tests/cavc_tests.rs to bc-test-data.rs to match other crates
ounsworth Sep 9, 2026
097c09b
Renaming / readability of some of the SHA2 internal traits.
ounsworth Sep 9, 2026
55f53f7
mem_usage_benches: fence the valgrind and ms_print snippets as text, …
dghgit Sep 9, 2026
0156990
sha2: the partial-byte example's three bits are message bits, not pad…
dghgit Sep 9, 2026
213473c
sha2: quote the SHA-512/t IV Generation Function as FIPS 180-4 s. 5.3…
dghgit Sep 9, 2026
5fc1370
CLAUDE.md: the build and test gates need --workspace, the mem_usage_b…
dghgit Sep 9, 2026
b11f8f6
sha2: sha512t_h0 asserts three-digit t and formats it as three digits…
dghgit Sep 9, 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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ mutants.out*/

.idea/
.vscode/

# Claude Code: ignore personal/local state, but share team tooling
# (skills, slash commands, subagents, and project settings.json).
.claude/*
!.claude/settings.json
!.claude/skills/
!.claude/commands/
!.claude/agents/
.claude/settings.local.json
.claude 2/
26 changes: 18 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Common commands

Build / test / bench / docs run against the cargo workspace from the repo root:
Build / test / bench / docs run against the cargo workspace from the repo root. `--workspace` is
not optional: the root manifest is both the workspace and the umbrella `bouncycastle` package, so a
bare `cargo build` builds only that package (no `cli`, no benches) and a bare `cargo test` runs
**zero** tests and still exits 0, because the umbrella crate has none of its own.

```
cargo build # whole workspace incl. `bc-rust` CLI binary
cargo build --workspace # whole workspace incl. `bc-rust` CLI binary
cargo build -p bouncycastle-sha3 # one sub-crate
cargo test # all tests
cargo test --workspace # all tests
cargo test -p bouncycastle-mlkem # tests for one crate
cargo test -p bouncycastle-mlkem ml_kem_tests # one integration test file
cargo bench --all # all criterion benches
Expand All @@ -30,19 +33,25 @@ Quality / mutation testing:
cargo mutants # config in .cargo/mutants.toml (output: custom_mutants_output/)
```

Stack-memory benches are separate binaries under `mem_usage_benches/`:
Stack-memory benches are separate binaries under `mem_usage_benches/src/`, each declared as a
`[[bin]]` in that crate's `Cargo.toml`:

```
cargo run --release -p mem_usage_benches --bin bench_mlkem_mem_usage
cargo run --release -p mem_usage_benches --bin bench_mldsa_mem_usage
```

`mem_usage_benches/src/lib.rs` makes those sources modules of a lib target as well, so their `//!`
headers are rustdoc'd and any indented or fenced block in them is compiled as a Rust doctest. The
valgrind and `ms_print` recipes there are fenced as ```` ```text ```` for that reason — keep it that
way when adding a harness, or `cargo test --workspace` fails to compile them.

## Workspace architecture

The workspace has three top-level kinds of member:

1. `crypto/*` — one sub-crate per primitive (`sha2`, `sha3`, `hmac`, `hkdf`, `mlkem`, `mlkem_lowmemory`, `mldsa`, `mldsa_lowmemory`, `rng`, `hex`, `base64`, `utils`) plus the spine crates `core`, `core-test-framework`, and `factory`. Each crate is published as `bouncycastle-<name>` and depended on internally via the `workspace.dependencies` table in the root `Cargo.toml`.
2. `src/` — the umbrella `bouncycastle` crate, which is just `pub use` re-exports of every sub-crate (e.g. `bouncycastle::sha3`, `bouncycastle::mlkem`). It exists so downstream users can pull the whole library with one dependency; it has no code of its own.
1. `crypto/*` — one sub-crate per primitive (`sha2`, `sha3`, `sm3`, `hmac`, `hkdf`, `mlkem`, `mlkem_lowmemory`, `mldsa`, `mldsa_lowmemory`, `rng`, `hex`, `base64`, `utils`) plus the spine crates `core`, `core-test-framework`, and `factory`. Each crate is published as `bouncycastle-<name>` and depended on internally via the `workspace.dependencies` table in the root `Cargo.toml`.
2. `src/` — the umbrella `bouncycastle` crate, which is just `pub use` re-exports of every sub-crate (e.g. `bouncycastle::sha3`, `bouncycastle::sm3`, `bouncycastle::mlkem`). It exists so downstream users can pull the whole library with one dependency; it has no code of its own.
3. `cli/` — the `bc-rust` binary built on top of `bouncycastle`, exposing every primitive as a streaming stdin→stdout subcommand using `clap`.
4. `mem_usage_benches/` — stand-alone binary crates that measure peak stack usage of algorithms (cannot be done via criterion).

Expand Down Expand Up @@ -113,10 +122,11 @@ Rules when working from the downloaded copy:
## Notes on testing

- `cargo mutants` is expected to be run on each crate; surviving mutants must be investigated but not all need to die (e.g. XOR/OR equivalences in crypto code are acceptable). Config lives in `.cargo/mutants.toml` (output dir `custom_mutants_output/`).
- Behaviour-critical private functions can use in-file `#[cfg(test)] mod tests` blocks when they can't be exercised from outside the crate.
- Integration tests in `tests/` are preferred over in-file `#[cfg(test)] mod tests` blocks — see "Unit tests vs integration tests" in QUALITY_AND_STYLE.md for the reasoning and the exceptions. A unit test is justified for high-risk code that has known-answer values and cannot be reached through the public API; when you write one, all of its helpers go inside that `mod tests`.
- A property that can be asserted at compile time (`const _: () = assert!(...)`) stays a compile-time assertion even when a test also covers it: `cargo mutants` cannot see a const assertion fail, so pair the two rather than trading the guarantee for the coverage.
- For traits in `core`, the canonical tests live in `core-test-framework` and are invoked from each implementor's integration tests — don't duplicate them per-implementation.
- The per-width `impl Condition<W>` blocks in `crypto/utils/src/ct.rs` (and their test modules) are deliberately duplicated rather than macro-generated: `cargo mutants` cannot see into `macro_rules!` bodies, so a macro would hide the mask identities from mutation testing. Do not fold them back into a macro. Any change to one width in a group (i64/i32, u64/u32) must be applied to every width in that group.

## CI

The only workflow is `.github/workflows/publish_doc_benches_to_ghpages.yaml`: on every PR it builds rustdoc and runs `quality_stats.sh`; on `main` it additionally runs `cargo bench --all` and publishes docs, code stats, and benchmark results to GitHub Pages (`https://bcgit.github.io/bc-rust/`). There is no separate CI test/lint job — local `cargo test` is the gate.
The only workflow is `.github/workflows/publish_doc_benches_to_ghpages.yaml`: on every PR it builds rustdoc and runs `quality_stats.sh`; on `main` it additionally runs `cargo bench --all` and publishes docs, code stats, and benchmark results to GitHub Pages (`https://bcgit.github.io/bc-rust/`). There is no separate CI test/lint job — local `cargo test --workspace` is the gate, and nothing but a developer running it stands between a broken test and `main`.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ version = "0.1.3"

# *** Internal Dependencies ***
bouncycastle = { path = "./" }
bouncycastle-aes-lowmemory = { path = "./crypto/aes-lowmemory" }
bouncycastle-base64 = { path = "./crypto/base64" }
bouncycastle-modes = { path = "./crypto/modes" }
bouncycastle-core = { path = "crypto/core" }
bouncycastle-core-test-framework = { path = "./crypto/core-test-framework" }
bouncycastle-factory = { path = "./crypto/factory" }
Expand All @@ -20,9 +22,11 @@ bouncycastle-mlkem = { path = "./crypto/mlkem" }
bouncycastle-mlkem-lowmemory = { path = "./crypto/mlkem-lowmemory" }
bouncycastle-mldsa = { path = "./crypto/mldsa" }
bouncycastle-mldsa-lowmemory = { path = "./crypto/mldsa-lowmemory" }
bouncycastle-padding = { path = "./crypto/padding" }
bouncycastle-rng = { path = "./crypto/rng" }
bouncycastle-sha2 = { path = "./crypto/sha2" }
bouncycastle-sha3 = { path = "./crypto/sha3" }
bouncycastle-sm3 = { path = "./crypto/sm3" }
bouncycastle-utils = { path = "./crypto/utils" }


Expand All @@ -41,6 +45,7 @@ version.workspace = true
edition.workspace = true

[dependencies]
bouncycastle-aes-lowmemory.workspace = true
bouncycastle-base64.workspace = true
bouncycastle-core.workspace = true
bouncycastle-factory.workspace = true
Expand All @@ -51,6 +56,9 @@ bouncycastle-mldsa.workspace = true
bouncycastle-mldsa-lowmemory.workspace = true
bouncycastle-mlkem.workspace = true
bouncycastle-mlkem-lowmemory.workspace = true
bouncycastle-modes.workspace = true
bouncycastle-padding.workspace = true
bouncycastle-rng.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
bouncycastle-sm3.workspace = true
30 changes: 30 additions & 0 deletions QUALITY_AND_STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,36 @@ Note that rust macros tend not to play well with a lot of dev tooling for compil
`cargo mutants`, which is a good reason to avoid macros in core algorithm or data processing code. Macros can be used
more freely within test code.

## Unit tests vs integration tests

Unit tests are test code (and supporting helper functions) embedded in src/**.rs files. They have access to
crate-private or module-private functions and constants.

Integration tests are test code (and supporting helper functions) in tests/**.rs files. They test the crate's code from
the outside -- ie through its public APIs -- since tests/ is a separate crate from src/.

In general, integration tests are preferred over unit tests. This is for a number of reasons:

* To reduce reviewer burden; reviewers will typically focus more effort on the src/ than the tests/, so we want to keep
src/ as short as is reasonable.
* Usually it is easier to determine what is the correct behaviour at the public API level. For example, this is the
level at which we typically have KATs and test vectors.
* Tools like cargo mutants are very helpful at detecting branches that are not exercisable via the public APIs, which
often is an indicator that the branch isn't doing what you think it's doing, or is simply not useful and can be
deleted. Unit tests that bypass the public APIs to pin these sorts of branches obscure the fact that this code is
unreachable.

Unit tests are reasonable to include in the following cases:

* There is high-risk code (usually meaning that it is complex code whose behaviour is not obvious from inspection) where
unit tests help to document the behaviour and protect against accidental breakage via a benign-looking change.
* AND where known answer tests are available.
* AND where this behaviour cannot be tested from integration tests.

When writing unit tests, they should be contained with an `mod tests` at the bottom of the file, and ALL helper
functions that support the unit tests must be contained within that module. The intention is to clearly signal to a code
reviewer what is test code vs functional code.

# Docs

## Usage Examples
Expand Down
Loading
Loading