Skip to content
Open
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
55 changes: 43 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Required reading

This file is a *map*, not a rulebook. It records repo mechanics — commands, layout, where things live, how to work
here. The project's binding standards live in their own documents, which are authoritative and are updated
independently of this file. Read them; do not infer their contents from this file, and do not work from memory of a
previous session's reading of them.

- **[QUALITY_AND_STYLE.md](QUALITY_AND_STYLE.md) — read before writing or changing code, and before reviewing a
diff.** The authority on architecture, crate and API shape, naming conventions, fallibility, macros, what tests and
benchmarks a crate owes, and which sections crate docs must have. Its own opening line invites an AI to review a PR
against it, so treat it as exactly that checklist.
- **[CONTRIBUTING.md](CONTRIBUTING.md) — read before writing a commit message, opening a PR, or advising on how a
change gets merged.** The authority on coding philosophy, PR hygiene and self-review, the quality bar a submission
must clear to be accepted, how merges actually happen in this project, and the AI policy. That policy places
requirements on the commit messages and PR descriptions of AI-assisted work, which covers anything written here:
never compose a commit message or PR description for this repo without checking it first. It links onward to
[SECURITY.md](SECURITY.md) for anything security-sensitive and [ISSUES_STYLE_GUIDE.md](ISSUES_STYLE_GUIDE.md) for
issue and sub-issue structure.
- **[INTRODUCTION.md](INTRODUCTION.md) — read for design intent**, when a change touches public API shape or you need
the reasoning behind a convention rather than the convention itself.

Where this file and one of those documents disagree, the document wins — and say so, so the stale line here gets
fixed.

## Toolchain

- Uses Rust **nightly** (pinned in `rust-toolchain.toml`) — `core/src/lib.rs` uses `#![feature(adt_const_params)]`.
Expand Down Expand Up @@ -69,23 +93,27 @@ crypto/<name>/

`#![no_std]` is the long-term goal but the `core` crate still has a `Vec`-removal TODO blocking it (see the comment at the top of `crypto/core/src/lib.rs`). Don't add new `Vec` usage where a const-sized array would do.

## Project-specific conventions (from QUALITY_AND_STYLE.md and INTRODUCTION.md)
## Project-specific conventions

The house rules are deliberately **not** reproduced here — see [Required reading](#required-reading) above.
QUALITY_AND_STYLE.md governs API shape, naming, fallibility, macro use, and the tests, benches and doc sections a
crate owes; CONTRIBUTING.md governs what a submission must satisfy to be accepted. Both cover ground that is easy to
violate without noticing, so read them at the start of a session that will touch code rather than guessing which
conventions apply.

These are non-obvious house rules — follow them when writing or modifying code:
Repo mechanics behind those rules, which the documents don't spell out:

- **No `unsafe`, no runtime third-party deps.** `#![forbid(unsafe_code)]` is required at every crate's `lib.rs`. Avoid adding any non-internal runtime dependency; dev/bench dependencies (`criterion`, `clap`) are fine.
- **Push errors to compile time.** Prefer `&[u8; N]` over `&[u8]` + length-check, prefer the typestate pattern over runtime "initialized" booleans. `Result` should only carry truly-uncontrollable failures (bad user input, RNG init failure). If you're returning `Result` for something the caller can't reasonably hit with valid usage, redesign the signature instead. Run `./dev_scripts/quality_stats.sh` before and after to confirm you haven't increased unwrap/`Err()` counts.
- **No `init()` / `reset()`; `do_final` takes `self` by value.** Constructors set up state; consumption methods consume. This is the deliberate departure from other Bouncy Castle ports. Stateful builder-style patterns are discouraged.
- **One-shot static APIs are the default.** Every primitive should expose a take-data-return-result static method in addition to any streaming API.
- **Sensitive types impl `core::Secret` (and its supertraits).** Anything that holds key material needs this — don't reach for raw byte arrays for secrets.
- **`unwrap()` requires justification.** Either a preceding check that proves success, or an inline comment explaining why it's infallible.
- **Spec correspondence in comments.** Code that mirrors a FIPS/NIST/RFC spec should be commented line-by-line against the spec, citing section/algorithm/step numbers. Any deliberate deviation must be called out and justified. The "would 6-months-from-now me need >10 minutes to re-understand this?" check is the bar. Never write or check these comments from memory — see "Working from specifications" below.
- **Every primitive crate must ship: tests (`src/tests` or `tests/`), criterion benches in `benches/`, and a CLI subcommand.** Stack-memory characteristics matter — algorithms with non-trivial stack usage get a `mem_usage_benches/` harness.
- **CLI commands stream.** The `cli/` binary's design is stdin→stdout with ~1 KB buffers so commands compose in shell pipelines; preserve that when adding subcommands.
- **Crate docs must include sections:** "Usage Examples", "Memory Usage" (stack-usage table), and usually "Security Considerations".
- `./dev_scripts/quality_stats.sh` produces the fallibility metrics both documents ask you to check. Run it before
and after a change and compare, rather than eyeballing the diff.
- **CLI commands stream.** The `cli/` binary is stdin→stdout with ~1 KB buffers so commands compose in shell
pipelines; preserve that when adding subcommands.
- Trait → factory → CLI is the wiring path for a new primitive; see [the workspace architecture](#the-core--core-test-framework--factory-spine) above for the crates involved.

## Working from specifications

QUALITY_AND_STYLE.md is where the requirement for spec-corresponding comments and justified deviations lives. This
section is only about *how* to satisfy it without introducing errors.

**Never cite, paraphrase, or implement a specification from recall.** Model recall of RFC text, FIPS algorithm steps, NIST parameter tables, and section numbering is unreliable — plausible-looking but wrong step numbers and subtly wrong constants are the failure mode. Before writing or reviewing any code, comment, or doc that references a spec, download a fresh copy and read the relevant part of it.

Where to get them:
Expand All @@ -112,6 +140,9 @@ Rules when working from the downloaded copy:

## Notes on testing

What a crate must be tested against — including the mutation-testing expectation, the trait test framework, and the
external vector suites — is specified in QUALITY_AND_STYLE.md and CONTRIBUTING.md. Repo-specific mechanics:

- `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.
- 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.
Expand Down
45 changes: 30 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ regarded as one.
There is a time and a place for "Move fast and break things", but the source code of a crypto library is not one of
them.

This project takes the philosophy that taking the time to do things right pays off in the long run, both in terms of
the runtime and memory footprint of the code, and it terms of the time required for a future maintainer to get up to
speed with the code
and avoid introducing bugs due to the code being hard to understand.
This project takes the philosophy that taking the time to do things right pays off in the long run, both in terms of the
runtime and memory footprint of the code, and it terms of the time required for a future maintainer to get up to speed
with the code and avoid introducing bugs due to the code being hard to understand.

Some specifics:

Expand Down Expand Up @@ -80,22 +79,39 @@ For minor updates, you can instead choose to create an issue with short snippets
For more information, refer to the Bouncy Castle documentation
on [Getting Started with Bouncy Castle](https://doc.primekey.com/bouncycastle/introduction#Introduction-GettingStartedwithBouncyCastle).

### Merging a pull request

While this project uses GitHub for its issue tracking and Pull Request reviews, GitHub is a read-only mirror of a
private upstream git server. Therefore, all PRs need to be merged manually to the private server by a committer.

Merge checklist for committers:

* Merges should be done as a squash merge both to reduce the overall number of commits in the git tree, and so that only
the final version of a feature or bug fix gets merged; the intermediate sequence of working commits could be useful
for a PR reviewer, but not once merged (and it is persisted in the PR branch anyway).
* Committers may make stylistic changes while processing the merge.
* Attributing the contributor: if the PR was submitted by an external contributor, they should be recognized
in [CONTRIBUTORS.md], and, if possible, preserving their commit will give them credit for the contribution in Github.

### Creating sub-issues

When an issue requires a large amount of time or code changes to complete, it may be convenient for a contributor to break it up into distinct sub-issues, which can each be addressed by a separate pull request. This avoids reviewers managing very large PRs, or submitters needing to frequently resolve merge conflicts in their branches. If you would like to break up issue into sub-issues, see the instructions in [Issues Style Guide](ISSUES_STYLE_GUIDE.md).
When an issue requires a large amount of time or code changes to complete, it may be convenient for a contributor to
break it up into distinct sub-issues, which can each be addressed by a separate pull request. This avoids reviewers
managing very large PRs, or submitters needing to frequently resolve merge conflicts in their branches. If you would
like to break up issue into sub-issues, see the instructions in [Issues Style Guide](ISSUES_STYLE_GUIDE.md).

### Quality Standards

Except where otherwise noted, all crates must have:

* benchmarks
* unit tests that (mostly) satisfy cargo mutants
* lib.rs needs to compile with: #![forbid(missing_docs)], #![no_std]
* Fallibility: as much as humanly possible, Result and unwrap() should be used for "Bad input data" type things and
not "Programmer didn't read the docs" type things. Things like \[u8]'s of the wrong length, or trying to call an
algorithm with a key of the wrong parameter set should be detected at compile time via the typing system and should
not require a Result / unwrap() mechanism. Please run `./dev_scripts/quality_stats.sh` before and after your change to
see if you have increased the fallibility of the code you changed.
* lib.rs needs to compile with: #![forbid (missing_docs)], #![no_std]
* Fallibility: as much as humanly possible, Result and unwrap () should be used for "Bad input data" type things and not
"Programmer didn't read the docs" type things. Things like \[u8]'s of the wrong length, or trying to call an algorithm
with a key of the wrong parameter set should be detected at compile time via the typing system and should not require
a Result / unwrap () mechanism. Please run `./dev_scripts/quality_stats.sh` before and after your change to see if you
have increased the fallibility of the code you changed.

Code submissions that do not meet these standards, or that require significant effort from the maintainers in order to
meet these standards, will not be accepted.
Expand Down Expand Up @@ -160,10 +176,9 @@ rejected for this reason.

Specifics:

What counts as "non-trivial"?
A non-trivial portion of a submission has been created with an AI tool when it has generated meaningful code, logic, or
documentation — not merely assisted with trivial tasks like autocompletion of a single line, reformatting, or
spell-checking. If in doubt, declare it as a non-trivial contribution.
What counts as "non-trivial"? A non-trivial portion of a submission has been created with an AI tool when it has
generated meaningful code, logic, or documentation — not merely assisted with trivial tasks like autocompletion of a
single line, reformatting, or spell-checking. If in doubt, declare it as a non-trivial contribution.

How to declare it:
The commit message or body of the pull request must include a line of the form `Assisted-by: {agent}:{model}`; for
Expand Down
5 changes: 3 additions & 2 deletions cli/src/hkdf_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bouncycastle::core::key_material::{
};
use bouncycastle::hex;
use bouncycastle::hkdf;
use bouncycastle::sha2::hkdf::{HKDF_SHA256, HKDF_SHA512};

pub(crate) fn hkdf_cmd(
hkdfname: &str,
Expand Down Expand Up @@ -70,14 +71,14 @@ pub(crate) fn hkdf_cmd(

match hkdfname {
"HKDF-SHA256" => {
let mut h = hkdf::HKDF_SHA256::new();
let mut h = HKDF_SHA256::new();
h.do_extract_init(&salt_key).unwrap();
h.do_extract_update_bytes(ikm_bytes.as_slice()).unwrap();
h.do_extract_update_bytes(additional_input_bytes.as_slice()).unwrap();
h.do_extract_final_out(&mut out_key).unwrap();
}
"HKDF-SHA512" => {
let mut h = hkdf::HKDF_SHA512::new();
let mut h = HKDF_SHA512::new();
h.do_extract_init(&salt_key).unwrap();
h.do_extract_update_bytes(ikm_bytes.as_slice()).unwrap();
h.do_extract_update_bytes(additional_input_bytes.as_slice()).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion cli/src/mac_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use bouncycastle::core::key_material::{
};
use bouncycastle::core::traits::MAC;
use bouncycastle::hex;
use bouncycastle::hmac::{HMAC_SHA256, HMAC_SHA512};
use bouncycastle::sha2::hmac::{HMAC_SHA256, HMAC_SHA512};

pub(crate) enum HMACVariant {
SHA256,
Expand Down
2 changes: 0 additions & 2 deletions crypto/factory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ edition.workspace = true

[dependencies]
bouncycastle-core.workspace = true
bouncycastle-hkdf.workspace = true
bouncycastle-hmac.workspace = true
bouncycastle-sha2.workspace = true
bouncycastle-sha3.workspace = true
bouncycastle-rng.workspace = true
Expand Down
19 changes: 9 additions & 10 deletions crypto/factory/src/kdf_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! let seed_key = KeyMaterial256::from_rng(&mut bouncycastle_rng::DefaultRNG::default()).unwrap();
//! let additional_input: &[u8] = b"some additional input";
//!
//! let mut h = bouncycastle_factory::kdf_factory::KDFFactory::new(bouncycastle_hkdf::HKDF_SHA256_NAME).unwrap();
//! let mut h = bouncycastle_factory::kdf_factory::KDFFactory::new(bouncycastle_sha2::hkdf::HKDF_SHA256_NAME).unwrap();
//! let new_key = h.derive_key(&seed_key, additional_input).unwrap();
//! ```
//!
Expand Down Expand Up @@ -51,8 +51,7 @@ use crate::{AlgorithmFactory, DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, Factory
use bouncycastle_core::errors::KDFError;
use bouncycastle_core::key_material::KeyMaterialTrait;
use bouncycastle_core::traits::{KDF, SecurityStrength};
use bouncycastle_hkdf as hkdf;
use bouncycastle_hkdf::{HKDF_SHA256_NAME, HKDF_SHA512_NAME};
use bouncycastle_sha2::hkdf::{HKDF_SHA256, HKDF_SHA256_NAME, HKDF_SHA512, HKDF_SHA512_NAME};
use bouncycastle_sha3 as sha3;
use bouncycastle_sha3::{
SHA3_224_NAME, SHA3_256_NAME, SHA3_384_NAME, SHA3_512_NAME, SHAKE128_NAME, SHAKE256_NAME,
Expand All @@ -63,10 +62,10 @@ use bouncycastle_sha3::{
pub enum KDFFactory {
///
#[allow(non_camel_case_types)]
HKDF_SHA256(hkdf::HKDF_SHA256),
HKDF_SHA256(HKDF_SHA256),
///
#[allow(non_camel_case_types)]
HKDF_SHA512(hkdf::HKDF_SHA512),
HKDF_SHA512(HKDF_SHA512),
///
SHA3_224(sha3::SHA3_224),
///
Expand All @@ -83,26 +82,26 @@ pub enum KDFFactory {

impl Default for KDFFactory {
fn default() -> Self {
Self::HKDF_SHA512(hkdf::HKDF_SHA512::new())
Self::HKDF_SHA512(HKDF_SHA512::new())
}
}

impl AlgorithmFactory for KDFFactory {
fn default_128_bit() -> Self {
Self::HKDF_SHA256(hkdf::HKDF_SHA256::new())
Self::HKDF_SHA256(HKDF_SHA256::new())
}

fn default_256_bit() -> Self {
Self::HKDF_SHA512(hkdf::HKDF_SHA512::new())
Self::HKDF_SHA512(HKDF_SHA512::new())
}

fn new(alg_name: &str) -> Result<Self, FactoryError> {
match alg_name {
DEFAULT => Ok(KDFFactory::default()),
DEFAULT_128_BIT => Ok(KDFFactory::default_128_bit()),
DEFAULT_256_BIT => Ok(KDFFactory::default_256_bit()),
HKDF_SHA256_NAME => Ok(Self::HKDF_SHA256(hkdf::HKDF_SHA256::new())),
HKDF_SHA512_NAME => Ok(Self::HKDF_SHA512(hkdf::HKDF_SHA512::new())),
HKDF_SHA256_NAME => Ok(Self::HKDF_SHA256(HKDF_SHA256::new())),
HKDF_SHA512_NAME => Ok(Self::HKDF_SHA512(HKDF_SHA512::new())),
SHA3_224_NAME => Ok(Self::SHA3_224(sha3::SHA3_224::new())),
SHA3_256_NAME => Ok(Self::SHA3_256(sha3::SHA3_256::new())),
SHA3_384_NAME => Ok(Self::SHA3_384(sha3::SHA3_384::new())),
Expand Down
Loading
Loading