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
26 changes: 25 additions & 1 deletion crates/protocols/hpke/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- [#1539](https://github.com/celabshq/libcrux/pull/1539): Support for the
post-quantum and PQ/T-hybrid algorithms of
[draft-ietf-hpke-pq](https://datatracker.ietf.org/doc/html/draft-ietf-hpke-pq-04),
in the **libcrux provider only**, behind the new `draft-ietf-hpke-pq` feature.
Validated against the draft's published test vectors.
- Single-stage KDFs `KdfAlgorithm::Shake128 = 0x0010` and
`KdfAlgorithm::Shake256 = 0x0011`, with the corresponding one-stage HPKE
key schedule (`KeySchedule`, `Export`, `DeriveKeyPair`).
- ML-KEM-512 (`0x0040`), ML-KEM-768 (`0x0041`), and ML-KEM-1024 (`0x0042`) as
HPKE KEMs.
- The PQ/T-hybrid KEMs `KemAlgorithm::MlKem768P256 = 0x0050`,
`KemAlgorithm::MlKem1024P384 = 0x0051`, and `MLKEM768-X25519` (`0x647a`,
via X-Wing draft-06), per
[draft-irtf-cfrg-concrete-hybrid-kems-03](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-concrete-hybrid-kems-03).

### Deprecated

- The `draft-connolly-cfrg-hpke-mlkem` feature is deprecated in favour of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also have a PR number?

`draft-ietf-hpke-pq` (it emits a build warning, and the two features cannot be
enabled at the same time).

## [0.7.0] (2026-07-15)

### Changed
Expand All @@ -13,7 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#147](https://github.com/celabshq/hpke-rs/pull/147): Add P384 and P521 DHKEM support for the libcrux provider via RustCrypto crates guarded behind the `libcrux-rustcrypto-p-curves` feature flag.
- [#146](https://github.com/celabshq/hpke-rs/pull/146): Add support for ML-KEM768 and ML-KEM1024 gated behind the `draft-connolly-cfrg-hpke-mlkem` feature flag.


## [0.6.1] - 2026-03-20

- Update crypto providers
Expand Down
16 changes: 16 additions & 0 deletions crates/protocols/hpke/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ libcrux-sha3.workspace = true
hpke-rs-rust-crypto = { workspace = true, optional = true }
hpke-rs-libcrux = { workspace = true, optional = true }

# `--all-features` does not build: `draft-connolly-cfrg-hpke-mlkem` and
# `draft-ietf-hpke-pq` are mutually exclusive. Pin a working set for docs.rs.
[package.metadata.docs.rs]
no-default-features = true
features = ["std", "serialization", "hazmat", "rustcrypto", "libcrux", "draft-ietf-hpke-pq"]

[features]
default = []
std = [
Expand All @@ -35,6 +41,10 @@ libcrux = ["dep:hpke-rs-libcrux"]
libcrux-rustcrypto-p-curves = ["hpke-rs-libcrux?/rustcrypto-p-curves"]
experimental = ["hpke-rs-rust-crypto?/experimental"]
draft-connolly-cfrg-hpke-mlkem = ["hpke-rs-libcrux?/draft-connolly-cfrg-hpke-mlkem"]
draft-ietf-hpke-pq = [
"libcrux",
"hpke-rs-libcrux/draft-ietf-hpke-pq"
]

hpke-test = ["std"]
hpke-test-prng = [
Expand All @@ -58,6 +68,12 @@ criterion = { version = "0.8", features = ["html_reports"] }
[[bench]]
name = "bench"
harness = false
required-features = ["libcrux", "rustcrypto"]

[[bench]]
name = "bench_pq"
harness = false
required-features = ["draft-ietf-hpke-pq", "libcrux"]

[[bench]]
name = "manual_benches"
Expand Down
241 changes: 22 additions & 219 deletions crates/protocols/hpke/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use hpke_rs::{prelude::*, test_util::hex_to_bytes};
//! Benchmarks for classic (DH-based) HPKE ciphersuites.
//!
//! Run with: `cargo bench --bench bench --features libcrux,rustcrypto`

use criterion::{criterion_group, criterion_main, Criterion};
use hpke_rs::prelude::*;
use hpke_rs_crypto::{
types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm},
types::{KdfAlgorithm, KemAlgorithm},
HpkeCrypto,
};
use hpke_rs_libcrux::HpkeLibcrux;
use hpke_rs_rust_crypto::*;
use rand::Rng;

mod common;
use common::{bench_suite, AEAD_IDS};

const MODES: [Mode; 4] = [
HpkeMode::Base,
HpkeMode::Auth,
HpkeMode::Psk,
HpkeMode::AuthPsk,
];
const AEAD_IDS: [AeadAlgorithm; 3] = [
AeadAlgorithm::Aes128Gcm,
AeadAlgorithm::Aes256Gcm,
AeadAlgorithm::ChaCha20Poly1305,
];
const KDF_IDS: [KdfAlgorithm; 3] = [
KdfAlgorithm::HkdfSha256,
KdfAlgorithm::HkdfSha384,
Expand All @@ -33,10 +34,7 @@ const KEM_IDS: [KemAlgorithm; 6] = [
KemAlgorithm::DhKem448,
];

const AEAD_PAYLOAD: usize = 128;
const AEAD_AAD: usize = 48;

fn benchmark<Crypto: HpkeCrypto + 'static>(c: &mut Criterion) {
fn benchmark_classic<Crypto: HpkeCrypto + 'static>(c: &mut Criterion) {
for hpke_mode in MODES {
for aead_mode in AEAD_IDS {
if Crypto::supports_aead(aead_mode).is_err() {
Expand All @@ -50,216 +48,21 @@ fn benchmark<Crypto: HpkeCrypto + 'static>(c: &mut Criterion) {
if Crypto::supports_kem(kem_mode).is_err() {
continue;
}
let mut hpke = Hpke::<Crypto>::new(hpke_mode, kem_mode, kdf_mode, aead_mode);
let label = format!("{} {}", Crypto::name(), hpke);
let kp = hpke.generate_key_pair().unwrap();
let enc = kp.public_key().as_slice();
let kp_r = hpke.generate_key_pair().unwrap();
let sk_rm = kp_r.private_key();
let pk_rm = kp_r.public_key();
let info = hex_to_bytes("4f6465206f6e2061204772656369616e2055726e");
let psk = if hpke_mode == HpkeMode::AuthPsk || hpke_mode == HpkeMode::Psk {
Some(hex_to_bytes(
"0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82",
))
} else {
None
};
let psk_id = if hpke_mode == HpkeMode::AuthPsk || hpke_mode == HpkeMode::Psk {
Some(hex_to_bytes("456e6e796e20447572696e206172616e204d6f726961"))
} else {
None
};
let (pk_sm, sk_sm) =
if hpke_mode == HpkeMode::AuthPsk || hpke_mode == HpkeMode::Auth {
let kp = hpke.generate_key_pair().unwrap();
(
Some(kp.public_key().clone()),
Some(kp.private_key().clone()),
)
} else {
(None, None)
};

let mut group = c.benchmark_group(label.to_string());
group.bench_function("Setup Sender", |b| {
b.iter(|| {
let mut hpke =
Hpke::<Crypto>::new(hpke_mode, kem_mode, kdf_mode, aead_mode);
hpke.setup_sender(
pk_rm,
&info,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
sk_sm.as_ref(),
)
.unwrap();
})
});
group.bench_function("Setup Receiver", |b| {
b.iter(|| {
let hpke =
Hpke::<Crypto>::new(hpke_mode, kem_mode, kdf_mode, aead_mode);
hpke.setup_receiver(
enc,
sk_rm,
&info,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
pk_sm.as_ref(),
)
.unwrap();
})
});

group.bench_function(format!("Seal {}({})", AEAD_PAYLOAD, AEAD_AAD), |b| {
b.iter_batched(
|| {
let mut hpke =
Hpke::<Crypto>::new(hpke_mode, kem_mode, kdf_mode, aead_mode);
let (_enc, context) = hpke
.setup_sender(
pk_rm,
&info,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
sk_sm.as_ref(),
)
.unwrap();
let mut aad = vec![0u8; AEAD_AAD];
rand::rng().fill_bytes(&mut aad);
let mut ptxt = vec![0u8; AEAD_PAYLOAD];
rand::rng().fill_bytes(&mut ptxt);
(context, aad, ptxt)
},
|(mut context, aad, ptxt)| {
let _ctxt = context.seal(&aad, &ptxt).unwrap();
},
BatchSize::SmallInput,
)
});
group.bench_function(format!("Open {}({})", AEAD_PAYLOAD, AEAD_AAD), |b| {
b.iter_batched(
|| {
let mut hpke =
Hpke::<Crypto>::new(hpke_mode, kem_mode, kdf_mode, aead_mode);
let (enc, mut sender_context) = hpke
.setup_sender(
pk_rm,
&info,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
sk_sm.as_ref(),
)
.unwrap();
let mut aad = vec![0u8; AEAD_AAD];
rand::rng().fill_bytes(&mut aad);
let mut ptxt = vec![0u8; AEAD_PAYLOAD];
rand::rng().fill_bytes(&mut ptxt);
let ctxt = sender_context.seal(&aad, &ptxt).unwrap();

let context = hpke
.setup_receiver(
&enc,
sk_rm,
&info,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
pk_sm.as_ref(),
)
.unwrap();
(context, aad, ctxt)
},
|(mut context, aad, ctxt)| {
let _ctxt_out = context.open(&aad, &ctxt).unwrap();
},
BatchSize::SmallInput,
)
});

group.bench_function(
format!("Single-Shot Seal {}({})", AEAD_PAYLOAD, AEAD_AAD),
|b| {
b.iter_batched(
|| {
let hpke = Hpke::<Crypto>::new(
hpke_mode, kem_mode, kdf_mode, aead_mode,
);
let mut aad = vec![0u8; AEAD_AAD];
rand::rng().fill_bytes(&mut aad);
let mut ptxt = vec![0u8; AEAD_PAYLOAD];
rand::rng().fill_bytes(&mut ptxt);
(hpke, aad, ptxt)
},
|(mut hpke, aad, ptxt)| {
let _ctxt = hpke
.seal(
pk_rm,
&info,
&aad,
&ptxt,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
sk_sm.as_ref(),
)
.unwrap();
},
BatchSize::SmallInput,
)
},
);
group.bench_function(
format!("Single-Shot Open {}({})", AEAD_PAYLOAD, AEAD_AAD),
|b| {
b.iter_batched(
|| {
let mut hpke = Hpke::<Crypto>::new(
hpke_mode, kem_mode, kdf_mode, aead_mode,
);
let (enc, mut sender_context) = hpke
.setup_sender(
pk_rm,
&info,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
sk_sm.as_ref(),
)
.unwrap();
let mut aad = vec![0u8; AEAD_AAD];
rand::rng().fill_bytes(&mut aad);
let mut ptxt = vec![0u8; AEAD_PAYLOAD];
rand::rng().fill_bytes(&mut ptxt);
let ctxt = sender_context.seal(&aad, &ptxt).unwrap();

(hpke, aad, ctxt, enc)
},
|(hpke, aad, ctxt, enc)| {
let _ctxt_out = hpke
.open(
&enc,
sk_rm,
&info,
&aad,
&ctxt,
psk.as_ref().map(Vec::as_ref),
psk_id.as_ref().map(Vec::as_ref),
pk_sm.as_ref(),
)
.unwrap();
},
BatchSize::SmallInput,
)
},
);
// Classic suites do not separately time key generation.
bench_suite::<Crypto>(c, hpke_mode, kem_mode, kdf_mode, aead_mode, false);
}
}
}
}
}

criterion_group!(
benches,
benchmark::<HpkeLibcrux>,
benchmark::<HpkeRustCrypto>,
);
fn bench_libcrux(c: &mut Criterion) {
benchmark_classic::<HpkeLibcrux>(c);
}

fn bench_rust_crypto(c: &mut Criterion) {
benchmark_classic::<HpkeRustCrypto>(c);
}

criterion_group!(benches, bench_libcrux, bench_rust_crypto,);
criterion_main!(benches);
Loading
Loading