Development
The library is one module. lib/hpke.mli is the contract and
lib/hpke.ml is the whole implementation: DHKEM, the RFC 9180
key schedule, contexts, and the single-shot API over Mirage Crypto
primitives.
Set up
git clone https://github.com/thevilledev/ocaml-hpke.git
cd ocaml-hpke
opam install . --deps-only --with-test --with-doc
Build and test
dune build @all @doc
dune runtest
dune runtest runs the Alcotest suite and, on OCaml 5.0 or
newer, the two-domain concurrency check. Generated odoc lands in
_build/default/_doc/_html/.
| RFC 9180 vectors | X25519 Base, PSK, Auth, and AuthPSK, and ChaCha20-Poly1305 Base fixtures. |
|---|---|
| Successor draft vectors | P-256 rejection sampling, empty and zero-byte AEAD inputs, empty and embedded-zero info, embedded-zero PSK inputs, and export-only. |
| Differential fixtures | The P-384 fixture generated by Go's crypto/hpke. |
| Suites | All 45 Base combinations, PSK, Auth, and AuthPSK across every KEM, and export-only agreement. |
| Invariants | Sequence retention after a failed open, malformed inputs, X25519 and X448 private-key clamping, single-shot error normalization, adversarial mismatches, and mismatched modes and senders. |
| Suite primitives | Algorithm sizes, bounds of the unlabeled KDF, and single-shot AEAD round trips, tampering, and length checks. |
| For testing | The deterministic senders: agreement with an ordinary receiver in all four modes, repeatability, and ephemeral, recipient, and sender keys of another KEM. |
| Properties | QCheck round-trip and peer-input totality. |
A second executable, test_vectors, replays the pinned
RFC 9180 corpus four ways: through the public API with fixed
randomness, through hpke.for_testing with the published
ephemeral private key, against the published key-schedule
intermediates for Kdf.extract and Kdf.expand,
and against every encryption record for Aead.seal and
Aead.open_.
Deterministic senders
HPKE requires a fresh, secret ephemeral key for every sender context,
and the public API always draws it from the generator it is given. Some
published vectors fix the ephemeral private key itself, including
those of protocols layered on HPKE such as Oblivious HTTP (RFC 9458).
The separate hpke.for_testing library reproduces them:
Hpke_for_testing.setup_base_sender suite ~ephemeral ~recipient ~info
setup_psk_sender, setup_auth_sender, and
setup_auth_psk_sender do the same for the other modes.
It is a separate findlib library so that a dependency on it is visible
in a dune file. Reusing or disclosing an ephemeral key
breaks the confidentiality of everything sealed under it; nothing
outside a test suite should link hpke.for_testing.
Fuzzing
dune build --profile fuzz fuzz/fuzz_hpke.exe
_build/default/fuzz/fuzz_hpke.exe --repeat 1000
The Crowbar targets cover the code paths an attacker controls: key
parsing for every KEM, receiver setup in all four modes on arbitrary
encapsulations, Auth-mode sender keys, and opening arbitrary
ciphertexts against a valid encapsulation. The
executable is behind the fuzz profile so a normal build
does not need Crowbar.
Formatting and metadata
dune fmt
opam lint hpke.opam
Formatting is ocamlformat 0.29.0 with the conventional
profile, installed through
opam install . --deps-only --with-dev-setup. CI runs
dune fmt and then requires a clean git diff,
so unformatted code fails rather than being reformatted for you.
Layout
lib/
hpke.mli public API and contract
hpke.ml DHKEM, key schedule, contexts
hpke_for_testing.mli hpke.for_testing: deterministic senders
test/
test_hpke.ml vectors, suites, invariants, properties
test_vectors.ml the pinned RFC 9180 corpus
concurrency/ two-domain contention, OCaml 5.0+
fuzz/
fuzz_hpke.ml Crowbar targets for peer input
tools/differential/go/ generator for the Go crypto/hpke fixture
test-vectors/ vector provenance
website/ this static documentation site
Continuous integration
The exact OCaml versions, fuzzing parameters, and dependency pins are
recorded in
.github/workflows/ci.yml.
test | Builds @all @doc and runs the suite with several OCaml versions on Linux and macOS. |
|---|---|
fuzz | Runs the Crowbar targets with a fixed seed, briefly on every pull request and push to main and for longer on the weekly schedule. |
packaging | Runs opam lint, then installs the package itself with opam install, tests and docs included. |
hygiene | opam lint, dune fmt, and a clean working tree. |
lower-bounds | Installs the exact declared minimums from hpke.opam on the oldest supported OCaml and runs the suite against them. |
The lower-bounds job is why the version constraints in
hpke.opam mean something: a floor that is never built is a
guess.
Test-vector policy
Fixtures are pinned to immutable commits and are never refreshed from a
moving branch. Changing one requires citing a standards revision and its
source commit; the current pins are recorded in
test-vectors/PROVENANCE.md.
Contributing
Changes to wire behavior need a vector, not an argument. A new suite needs a pinned fixture from an independent implementation or a published standard; a new mode belongs in the versioned module that names its standard. Additions to the public API should say what they make impossible, not only what they make possible.
Use issues for bugs and proposals, and the private advisory flow described on the security page for anything exploitable. Contributions are ISC.