Ciphersuites
Each registry is a closed OCaml variant, not an integer. Unknown
codepoints cannot be constructed at all; of_int is the only
way in from the wire, and it returns
Unsupported_algorithm for anything outside the tables
below.
let suite =
Suite.create ~kem:Kem.X25519 ~kdf:Kdf.Hkdf_sha256
~aead:Aead.Aes_128_gcm
let exporter =
Suite.export_only ~kem:Kem.P384 ~kdf:Kdf.Hkdf_sha384
Five KEMs, three KDFs, and three AEADs give 45 encryption suites; every one of them is round-tripped by the test suite in Base mode. Dropping the AEAD gives 15 further export-only suites.
KEM
| Constructor | RFC 9180 name | ID | Public / enc | Private | Secret |
|---|---|---|---|---|---|
Kem.P256 | DHKEM(P-256, HKDF-SHA256) | 0x0010 | 65 | 32 | 32 |
Kem.P384 | DHKEM(P-384, HKDF-SHA384) | 0x0011 | 97 | 48 | 48 |
Kem.P521 | DHKEM(P-521, HKDF-SHA512) | 0x0012 | 133 | 66 | 64 |
Kem.X25519 | DHKEM(X25519, HKDF-SHA256) | 0x0020 | 32 | 32 | 32 |
Kem.X448 | DHKEM(X448, HKDF-SHA512) | 0x0021 | 56 | 56 | 64 |
Sizes are bytes. An encapsulated key has the same size as a public key,
because it is one. Kem.public_key_size,
Kem.private_key_size, and
Kem.encapsulated_key_size report these at run time.
KDF
| Constructor | Name | ID | Hash size | Longest export |
|---|---|---|---|---|
Kdf.Hkdf_sha256 | HKDF-SHA256 | 0x0001 | 32 | 8,160 |
Kdf.Hkdf_sha384 | HKDF-SHA384 | 0x0002 | 48 | 12,240 |
Kdf.Hkdf_sha512 | HKDF-SHA512 | 0x0003 | 64 | 16,320 |
The export ceiling is HKDF's own: 255 times the hash size. Asking for
more, or for a negative length, returns
Export_length_out_of_range rather than a truncated answer.
AEAD
| Constructor | Name | ID | Key | Nonce | Tag | Longest plaintext |
|---|---|---|---|---|---|---|
Aead.Aes_128_gcm | AES-128-GCM | 0x0001 | 16 | 12 | 16 | 236 − 31 |
Aead.Aes_256_gcm | AES-256-GCM | 0x0002 | 32 | 12 | 16 | 236 − 31 |
Aead.Chacha20_poly1305 | ChaCha20-Poly1305 | 0x0003 | 32 | 12 | 16 | 238 − 64 |
Suite.export_only | Export-only | 0xFFFF | — | — | — | — |
Export-only is a distinct suite type rather than an AEAD value, so it
cannot be passed to seal or open_: the
capability parameter on Suite.t makes that a type error.
Its 0xFFFF codepoint still enters the key schedule's suite
identifier, as RFC 9180 requires, so export secrets are domain-separated
from encryption suites.
Modes
| Mode | ID | Status |
|---|---|---|
| Base | 0x00 | Implemented. |
| PSK | 0x01 | Implemented. Secrets of at least 32 bytes, non-empty identifier. |
| Auth | 0x02 | Implemented. The sender seals with its static private key; the recipient opens with that key's public half. |
| AuthPSK | 0x03 | Implemented. Auth and PSK together. |
Deliberate omissions
-
A wire format.
encapsulated_keyandciphertextstay separate fields. The library will not invent a framing that applications would then have to live with forever. - Compressed points. NIST public keys must be in uncompressed SEC1 form, which is what RFC 9180 specifies.
-
Secret printers and equality helpers. Private keys
and PSKs have no
ppand no comparison function, so they cannot casually reach a log line or a timing-variable comparison.
Validation
| Public keys | Exact encoded length. NIST keys must start with the uncompressed SEC1 marker and lie on the curve. X25519 and X448 low-order values are rejected when the key is used. |
|---|---|
| Private keys | Exact encoded length. NIST scalars must be non-zero and below the curve order, compared in constant time. X25519 and X448 scalars are clamped on parse and on output. |
| Encapsulated keys | Parsed as a public key for the suite's KEM. Context-level setup reports the failure structurally; single-shot opens normalize it. |
| Suite agreement | A recipient or sender key whose KEM differs from the suite's KEM yields Key_mismatch before any cryptography. |
| PSKs | Secret of at least 32 bytes and a non-empty identifier. A length check cannot establish entropy. |
| Derivation | derive_key_pair follows RFC 9180, including rejection sampling for the NIST curves; exhausting it yields Derive_key_pair_failure. |
Limits
- A context's sequence number is 96 bits. When it is exhausted, seal and open return
Message_limit_reachedinstead of reusing a nonce. - Plaintexts are bounded per AEAD, as tabulated above; longer input returns
Plaintext_too_long. - A ciphertext shorter than the AEAD tag is rejected as
Open_errorwithout consuming a nonce. - Exports run from 0 to 255 times the KDF hash size, and never change sequence state.
Versioning
Wire behavior lives under Hpke.Rfc9180 and will not be
changed by a future HPKE standard. A successor standard can be added as
a second versioned module, so applications choose when to move rather
than discovering that an upgrade changed what their peers see.
The successor draft, draft-ietf-hpke-hpke, removes the
Auth and AuthPSK modes and reserves their identifiers. They stay in
Hpke.Rfc9180; a module for the successor standard would
not offer them.