diff --git a/crates/protocols/hpke/CHANGELOG.md b/crates/protocols/hpke/CHANGELOG.md index d8a21d5f0..72b8371ee 100644 --- a/crates/protocols/hpke/CHANGELOG.md +++ b/crates/protocols/hpke/CHANGELOG.md @@ -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 + `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 @@ -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 diff --git a/crates/protocols/hpke/Cargo.toml b/crates/protocols/hpke/Cargo.toml index 1b42f7ecf..f3746bb7a 100644 --- a/crates/protocols/hpke/Cargo.toml +++ b/crates/protocols/hpke/Cargo.toml @@ -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 = [ @@ -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 = [ @@ -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" diff --git a/crates/protocols/hpke/benches/bench.rs b/crates/protocols/hpke/benches/bench.rs index 10a3729d4..09853e6e7 100644 --- a/crates/protocols/hpke/benches/bench.rs +++ b/crates/protocols/hpke/benches/bench.rs @@ -1,12 +1,18 @@ -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, @@ -14,11 +20,6 @@ const MODES: [Mode; 4] = [ HpkeMode::Psk, HpkeMode::AuthPsk, ]; -const AEAD_IDS: [AeadAlgorithm; 3] = [ - AeadAlgorithm::Aes128Gcm, - AeadAlgorithm::Aes256Gcm, - AeadAlgorithm::ChaCha20Poly1305, -]; const KDF_IDS: [KdfAlgorithm; 3] = [ KdfAlgorithm::HkdfSha256, KdfAlgorithm::HkdfSha384, @@ -33,10 +34,7 @@ const KEM_IDS: [KemAlgorithm; 6] = [ KemAlgorithm::DhKem448, ]; -const AEAD_PAYLOAD: usize = 128; -const AEAD_AAD: usize = 48; - -fn benchmark(c: &mut Criterion) { +fn benchmark_classic(c: &mut Criterion) { for hpke_mode in MODES { for aead_mode in AEAD_IDS { if Crypto::supports_aead(aead_mode).is_err() { @@ -50,216 +48,21 @@ fn benchmark(c: &mut Criterion) { if Crypto::supports_kem(kem_mode).is_err() { continue; } - let mut hpke = Hpke::::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::::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::::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::::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::::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::::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::::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::(c, hpke_mode, kem_mode, kdf_mode, aead_mode, false); } } } } } -criterion_group!( - benches, - benchmark::, - benchmark::, -); +fn bench_libcrux(c: &mut Criterion) { + benchmark_classic::(c); +} + +fn bench_rust_crypto(c: &mut Criterion) { + benchmark_classic::(c); +} + +criterion_group!(benches, bench_libcrux, bench_rust_crypto,); criterion_main!(benches); diff --git a/crates/protocols/hpke/benches/bench_pq.rs b/crates/protocols/hpke/benches/bench_pq.rs new file mode 100644 index 000000000..11e7d1519 --- /dev/null +++ b/crates/protocols/hpke/benches/bench_pq.rs @@ -0,0 +1,60 @@ +//! Benchmarks for post-quantum HPKE ciphersuites (draft-ietf-hpke-pq). +//! +//! Run with: `cargo bench --bench bench_pq --features draft-ietf-hpke-pq,libcrux` + +use criterion::{criterion_group, criterion_main, Criterion}; +use hpke_rs::prelude::*; +use hpke_rs_crypto::{ + types::{KdfAlgorithm, KemAlgorithm}, + HpkeCrypto, +}; +use hpke_rs_libcrux::HpkeLibcrux; + +mod common; +use common::{bench_suite, AEAD_IDS}; + +/// One two-stage (HKDF) and one single-stage (SHAKE) KDF, to cover both +/// key-schedule shapes for the post-quantum suites. +const PQ_KDF_IDS: [KdfAlgorithm; 2] = [KdfAlgorithm::HkdfSha256, KdfAlgorithm::Shake256]; + +/// Post-quantum KEMs from `draft-ietf-hpke-pq`. +const PQ_KEM_IDS: &[KemAlgorithm] = &[ + KemAlgorithm::MlKem512, + KemAlgorithm::MlKem768, + KemAlgorithm::MlKem1024, + KemAlgorithm::MlKem768P256, + #[cfg(feature = "libcrux-rustcrypto-p-curves")] + KemAlgorithm::MlKem1024P384, + KemAlgorithm::XWingDraft06, +]; + +/// Benchmark the post-quantum ciphersuites. +/// +/// Restricted to `Base` mode: the PQ KEMs return `UnsupportedKemOperation` for the +/// `Auth`/`AuthPsk` modes, and PSK is out of scope here. Key generation is timed in +/// addition to the usual operations. +fn benchmark_post_quantum(c: &mut Criterion) { + for aead_mode in AEAD_IDS { + if Crypto::supports_aead(aead_mode).is_err() { + continue; + } + for kdf_mode in PQ_KDF_IDS { + if Crypto::supports_kdf(kdf_mode).is_err() { + continue; + } + for &kem_mode in PQ_KEM_IDS { + if Crypto::supports_kem(kem_mode).is_err() { + continue; + } + bench_suite::(c, HpkeMode::Base, kem_mode, kdf_mode, aead_mode, true); + } + } + } +} + +fn bench_pq(c: &mut Criterion) { + benchmark_post_quantum::(c); +} + +criterion_group!(benches, bench_pq,); +criterion_main!(benches); diff --git a/crates/protocols/hpke/benches/common/mod.rs b/crates/protocols/hpke/benches/common/mod.rs new file mode 100644 index 000000000..d4273ed2b --- /dev/null +++ b/crates/protocols/hpke/benches/common/mod.rs @@ -0,0 +1,283 @@ +//! Shared benchmark machinery for the classic (`bench`) and post-quantum +//! (`bench_pq`) HPKE benchmark binaries. +//! +//! The two benchmark targets differ only in which ciphersuites they iterate +//! over (and whether they time key generation); the per-ciphersuite work is +//! identical and lives here in [`bench_suite`]. + +use criterion::{BatchSize, Criterion}; +use hpke_rs::{prelude::*, Hpke}; +use hpke_rs_crypto::{ + types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}, + HpkeCrypto, +}; +use rand::Rng; + +pub const AEAD_IDS: [AeadAlgorithm; 3] = [ + AeadAlgorithm::Aes128Gcm, + AeadAlgorithm::Aes256Gcm, + AeadAlgorithm::ChaCha20Poly1305, +]; + +pub const AEAD_PAYLOAD: usize = 128; +pub const AEAD_AAD: usize = 48; + +pub fn hex_to_bytes(hex: &str) -> Vec { + hpke_rs::test_util::hex_to_bytes(hex) +} + +fn random_aad() -> Vec { + let mut aad = vec![0u8; AEAD_AAD]; + rand::rng().fill_bytes(&mut aad); + aad +} + +fn random_ptxt() -> Vec { + let mut ptxt = vec![0u8; AEAD_PAYLOAD]; + rand::rng().fill_bytes(&mut ptxt); + ptxt +} + +fn get_psk_params(mode: Mode) -> (Option>, Option>) { + if mode == HpkeMode::AuthPsk || mode == HpkeMode::Psk { + ( + Some(hex_to_bytes( + "0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82", + )), + Some(hex_to_bytes("456e6e796e20447572696e206172616e204d6f726961")), + ) + } else { + (None, None) + } +} + +fn get_sender_keypair( + mode: Mode, + hpke: &mut Hpke, +) -> (Option, Option) { + if mode == HpkeMode::AuthPsk || mode == HpkeMode::Auth { + let kp = hpke.generate_key_pair().unwrap(); + ( + Some(kp.public_key().clone()), + Some(kp.private_key().clone()), + ) + } else { + (None, None) + } +} + +/// Benchmark every HPKE operation for a single ciphersuite. +/// +/// When `bench_keygen` is set, a `Generate Key Pair` benchmark is added in front +/// of the others. This is used for the post-quantum suites, where key generation +/// is a non-trivial cost worth measuring on its own. +pub fn bench_suite( + c: &mut Criterion, + hpke_mode: Mode, + kem_mode: KemAlgorithm, + kdf_mode: KdfAlgorithm, + aead_mode: AeadAlgorithm, + bench_keygen: bool, +) { + let mut hpke = Hpke::::new(hpke_mode, kem_mode, kdf_mode, aead_mode); + let label = format!("{} {}", Crypto::name(), hpke); + + 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, psk_id) = get_psk_params(hpke_mode); + let (pk_sm, sk_sm) = get_sender_keypair::(hpke_mode, &mut hpke); + + let mut group = c.benchmark_group(label.to_string()); + + // Generate Key Pair (only when requested, e.g. for the PQ KEMs). + if bench_keygen { + group.bench_function("Generate Key Pair", |b| { + b.iter(|| { + let mut hpke = Hpke::::new(hpke_mode, kem_mode, kdf_mode, aead_mode); + let _kp = hpke.generate_key_pair().unwrap(); + }) + }); + } + + // Setup Sender + group.bench_function("Setup Sender", |b| { + b.iter(|| { + let mut hpke = Hpke::::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(); + }) + }); + + // Setup Receiver - uses iter_batched to generate proper encapsulation + group.bench_function("Setup Receiver", |b| { + b.iter_batched( + || { + let mut hpke = Hpke::::new(hpke_mode, kem_mode, kdf_mode, aead_mode); + let (enc, _) = 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(); + enc + }, + |enc| { + let hpke = Hpke::::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(); + }, + BatchSize::SmallInput, + ) + }); + + // Seal + group.bench_function(format!("Seal {}({})", AEAD_PAYLOAD, AEAD_AAD), |b| { + b.iter_batched( + || { + let mut hpke = Hpke::::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 aad = random_aad(); + let ptxt = random_ptxt(); + (context, aad, ptxt) + }, + |(mut context, aad, ptxt)| { + let _ctxt = context.seal(&aad, &ptxt).unwrap(); + }, + BatchSize::SmallInput, + ) + }); + + // Open + group.bench_function(format!("Open {}({})", AEAD_PAYLOAD, AEAD_AAD), |b| { + b.iter_batched( + || { + let mut hpke = Hpke::::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 aad = random_aad(); + let ptxt = random_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, + ) + }); + + // Single-Shot Seal + group.bench_function( + format!("Single-Shot Seal {}({})", AEAD_PAYLOAD, AEAD_AAD), + |b| { + b.iter_batched( + || { + let hpke = Hpke::::new(hpke_mode, kem_mode, kdf_mode, aead_mode); + let aad = random_aad(); + let ptxt = random_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, + ) + }, + ); + + // Single-Shot Open + group.bench_function( + format!("Single-Shot Open {}({})", AEAD_PAYLOAD, AEAD_AAD), + |b| { + b.iter_batched( + || { + let mut hpke = Hpke::::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 aad = random_aad(); + let ptxt = random_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, + ) + }, + ); +} diff --git a/crates/protocols/hpke/benches/manual_benches.rs b/crates/protocols/hpke/benches/manual_benches.rs index 473acc0e0..e06b91fa5 100644 --- a/crates/protocols/hpke/benches/manual_benches.rs +++ b/crates/protocols/hpke/benches/manual_benches.rs @@ -68,8 +68,6 @@ fn benchmark() { ); println!("{}", label); - 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(); @@ -117,10 +115,20 @@ fn benchmark() { let start = Instant::now(); for _ in 0..ITERATIONS { - let hpke = Hpke::::new(hpke_mode, kem_mode, kdf_mode, aead_mode); + let mut hpke = + Hpke::::new(hpke_mode, kem_mode, kdf_mode, aead_mode); + let (encapsulation, _) = 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 _receiver = hpke .setup_receiver( - enc, + &encapsulation, sk_rm, &info, psk.as_ref().map(Vec::as_ref), diff --git a/crates/protocols/hpke/libcrux_provider/Cargo.toml b/crates/protocols/hpke/libcrux_provider/Cargo.toml index 18ef6cca5..02874ba08 100644 --- a/crates/protocols/hpke/libcrux_provider/Cargo.toml +++ b/crates/protocols/hpke/libcrux_provider/Cargo.toml @@ -15,6 +15,7 @@ libcrux-ecdh.workspace = true libcrux-hkdf.workspace = true libcrux-kem.workspace = true libcrux-aead.workspace = true +libcrux-sha3.workspace = true libcrux-traits.workspace = true # RustCrypto curves not available in libcrux # TODO: These should be replaced with libcrux implementations as soon as they @@ -36,10 +37,23 @@ zeroize = "1.8.2" [dev-dependencies] criterion = { version = "0.8", features = ["html_reports"] } +# `--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", "draft-ietf-hpke-pq"] + [features] deterministic-prng = [] # ⚠️ FOR TESTING ONLY. rustcrypto-p-curves = ["dep:p384", "dep:p521"] +# DEPRECATED: superseded by `draft-ietf-hpke-pq`. Cannot be enabled together +# with it. Emits a build warning when used. draft-connolly-cfrg-hpke-mlkem = [] +# Post-quantum HPKE ciphersuites (SHAKE256 KDF, pure ML-KEM and the +# ML-KEM/ECDH hybrid KEMs) as used by the MLS PQ ciphersuites. +# For MLKEM1024-P384, "rustcrypto-p-curves" must be enabled as well for now. +# +draft-ietf-hpke-pq = [] std = [ "rand/std", "rand_chacha/std", diff --git a/crates/protocols/hpke/libcrux_provider/benches/bench_hkdf.rs b/crates/protocols/hpke/libcrux_provider/benches/bench_hkdf.rs index 741fc1ba7..111141640 100644 --- a/crates/protocols/hpke/libcrux_provider/benches/bench_hkdf.rs +++ b/crates/protocols/hpke/libcrux_provider/benches/bench_hkdf.rs @@ -14,7 +14,7 @@ fn criterion_benchmark(c: &mut Criterion) { (salt.clone(), ikm.clone()) }, |(salt, ikm)| { - let _ = HpkeLibcrux::kdf_extract(KdfAlgorithm::HkdfSha256, &salt, &ikm); + let _ = HpkeLibcrux::kdf_extract(TwoStageKdfAlgorithm::HkdfSha256, &salt, &ikm); }, BatchSize::SmallInput, ) @@ -29,7 +29,7 @@ fn criterion_benchmark(c: &mut Criterion) { (prk.clone(), info.clone()) }, |(prk, info)| { - let _ = HpkeLibcrux::kdf_expand(KdfAlgorithm::HkdfSha256, &prk, &info, 32); + let _ = HpkeLibcrux::kdf_expand(TwoStageKdfAlgorithm::HkdfSha256, &prk, &info, 32); }, BatchSize::SmallInput, ) diff --git a/crates/protocols/hpke/libcrux_provider/build.rs b/crates/protocols/hpke/libcrux_provider/build.rs new file mode 100644 index 000000000..d7b394492 --- /dev/null +++ b/crates/protocols/hpke/libcrux_provider/build.rs @@ -0,0 +1,10 @@ +fn main() { + // The `draft-connolly-cfrg-hpke-mlkem` feature is deprecated in favour of + // `draft-ietf-hpke-pq`. Warn at build time when it is enabled. + if std::env::var_os("CARGO_FEATURE_DRAFT_CONNOLLY_CFRG_HPKE_MLKEM").is_some() { + println!( + "cargo:warning=the `draft-connolly-cfrg-hpke-mlkem` feature is deprecated; \ + use `draft-ietf-hpke-pq` instead" + ); + } +} diff --git a/crates/protocols/hpke/libcrux_provider/src/lib.rs b/crates/protocols/hpke/libcrux_provider/src/lib.rs index ecc7a9cbe..b70dbc29c 100644 --- a/crates/protocols/hpke/libcrux_provider/src/lib.rs +++ b/crates/protocols/hpke/libcrux_provider/src/lib.rs @@ -2,13 +2,26 @@ #![cfg_attr(not(test), no_std)] extern crate alloc; +// The deprecated `draft-connolly-cfrg-hpke-mlkem` feature is superseded by +// `draft-ietf-hpke-pq`; the two use incompatible `DeriveKeyPair` / KDF semantics +// for the same ML-KEM code points and must not be enabled together. +#[cfg(all( + feature = "draft-connolly-cfrg-hpke-mlkem", + feature = "draft-ietf-hpke-pq" +))] +compile_error!( + "enable only one of `draft-connolly-cfrg-hpke-mlkem` (deprecated) or `draft-ietf-hpke-pq`" +); + use alloc::{format, string::String, vec::Vec}; use core::fmt::Display; use zeroize::Zeroize; use hpke_rs_crypto::{ error::Error, - types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}, + types::{ + AeadAlgorithm, KdfAlgorithm, KemAlgorithm, SingleStageKdfAlgorithm, TwoStageKdfAlgorithm, + }, HpkeCrypto, HpkeTestRng, }; @@ -48,7 +61,7 @@ impl HpkeCrypto for HpkeLibcrux { "Libcrux".into() } - fn kdf_extract(alg: KdfAlgorithm, salt: &[u8], ikm: &[u8]) -> Result, Error> { + fn kdf_extract(alg: TwoStageKdfAlgorithm, salt: &[u8], ikm: &[u8]) -> Result, Error> { let alg = kdf_algorithm_to_libcrux_hkdf_algorithm(alg); let mut prk = alloc::vec![0u8; alg.hash_len()]; libcrux_hkdf::extract(alg, &mut prk, salt, ikm) @@ -57,7 +70,7 @@ impl HpkeCrypto for HpkeLibcrux { } fn kdf_expand( - alg: KdfAlgorithm, + alg: TwoStageKdfAlgorithm, prk: &[u8], info: &[u8], output_size: usize, @@ -69,6 +82,12 @@ impl HpkeCrypto for HpkeLibcrux { Ok(okm) } + fn kdf_derive(alg: SingleStageKdfAlgorithm, ikm: &[u8], l: usize) -> Result, Error> { + // Single-stage (XOF) KDF: `Derive(ikm, L) = SHAKE(ikm, 8*L)`. + // See draft-ietf-hpke-pq Section 5. + Ok(shake_derive(alg, &[ikm], l)) + } + fn dh(alg: KemAlgorithm, pk: &[u8], sk: &[u8]) -> Result, Error> { match alg { #[cfg(feature = "rustcrypto-p-curves")] @@ -132,13 +151,18 @@ impl HpkeCrypto for HpkeLibcrux { prng: &mut Self::HpkePrng, ) -> Result<(Vec, Vec), Error> { match alg { - #[cfg(feature = "draft-connolly-cfrg-hpke-mlkem")] - KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { + #[cfg(any( + feature = "draft-connolly-cfrg-hpke-mlkem", + feature = "draft-ietf-hpke-pq" + ))] + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { let kem_alg = kem_key_type_to_libcrux_alg(alg)?; libcrux_kem::key_gen(kem_alg, prng) .map(|(sk, pk)| (pk.encode(), sk.encode())) .map_err(|e| Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e))) } + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => hybrid::key_gen(alg, prng), KemAlgorithm::XWingDraft06 => { let kem_alg = kem_key_type_to_libcrux_alg(alg)?; libcrux_kem::key_gen(kem_alg, prng) @@ -175,6 +199,21 @@ impl HpkeCrypto for HpkeLibcrux { fn kem_key_gen_derand(alg: KemAlgorithm, seed: &[u8]) -> Result<(Vec, Vec), Error> { match alg { + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => { + hybrid::key_gen_derand(alg, seed) + } + // hpke-pq: the ML-KEM decapsulation key is the 64-byte `(d, z)` seed; + // the public key is expanded from it and `kem_decaps` re-expands the + // seed to recover the full decapsulation key. + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { + let kem_alg = kem_key_type_to_libcrux_alg(alg)?; + let (_sk, pk) = libcrux_kem::key_gen_derand(kem_alg, seed).map_err(|e| { + Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e)) + })?; + Ok((pk.encode(), seed.to_vec())) + } #[cfg(feature = "rustcrypto-p-curves")] KemAlgorithm::DhKemP384 => { let chacha_seed = p_curve_key_gen_seed(alg, seed)?; @@ -213,7 +252,22 @@ impl HpkeCrypto for HpkeLibcrux { pk_r: &[u8], prng: &mut Self::HpkePrng, ) -> Result<(Vec, Vec), Error> { + // For known-answer tests the encapsulation randomness is injected via the + // test seed; run the PQ KEMs derandomized with it rather than drawing + // from the RNG. We pull exactly the KEM's randomness length — + // which, when the test seed is the vector's `ikmE`, is `ikmE` in order. + #[cfg(feature = "deterministic-prng")] + if let Some(n) = pq_encaps_randomness_len(alg) { + let mut randomness = alloc::vec![0u8; n]; + prng.try_fill_test_bytes(&mut randomness) + .map_err(|_| Error::InsufficientRandomness)?; + return kem_encaps_derand(alg, pk_r, &randomness); + } match alg { + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => { + hybrid::encaps(alg, pk_r, prng) + } #[cfg(feature = "rustcrypto-p-curves")] KemAlgorithm::DhKemP384 | KemAlgorithm::DhKemP521 => { let (enc, sk_e) = ::kem_key_gen(alg, prng)?; @@ -236,6 +290,24 @@ impl HpkeCrypto for HpkeLibcrux { fn kem_decaps(alg: KemAlgorithm, ct: &[u8], sk_r: &[u8]) -> Result, Error> { match alg { + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => { + hybrid::decaps(alg, ct, sk_r) + } + // hpke-pq: `sk_r` is the 64-byte ML-KEM seed; re-derive the full + // decapsulation key before decapsulating. + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { + let kem_alg = kem_key_type_to_libcrux_alg(alg)?; + let (sk, _pk) = libcrux_kem::key_gen_derand(kem_alg, sk_r).map_err(|e| { + Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e)) + })?; + let ct = libcrux_kem::Ct::decode(kem_alg, ct) + .map_err(|_| Error::KemInvalidCiphertext)?; + ct.decapsulate(&sk) + .map_err(|e| Error::CryptoLibraryError(format!("Decaps error {:?}", e))) + .map(|ss| ss.encode()) + } #[cfg(feature = "rustcrypto-p-curves")] KemAlgorithm::DhKemP384 | KemAlgorithm::DhKemP521 => { let dh = ::dh(alg, ct, sk_r)?; @@ -371,8 +443,22 @@ impl HpkeCrypto for HpkeLibcrux { } /// Returns an error if the KDF algorithm is not supported by this crypto provider. - fn supports_kdf(_: KdfAlgorithm) -> Result<(), Error> { - Ok(()) + fn supports_kdf(alg: KdfAlgorithm) -> Result<(), Error> { + match alg { + KdfAlgorithm::HkdfSha256 | KdfAlgorithm::HkdfSha384 | KdfAlgorithm::HkdfSha512 => { + Ok(()) + } + + #[cfg(feature = "draft-ietf-hpke-pq")] + KdfAlgorithm::Shake128 | KdfAlgorithm::Shake256 => Ok(()), + + #[cfg(not(feature = "draft-ietf-hpke-pq"))] + KdfAlgorithm::Shake128 | KdfAlgorithm::Shake256 => Err(Error::UnknownKdfAlgorithm), + + KdfAlgorithm::TurboShake128 | KdfAlgorithm::TurboShake256 => { + Err(Error::UnknownKdfAlgorithm) + } + } } /// Returns an error if the KEM algorithm is not supported by this crypto provider. @@ -381,21 +467,28 @@ impl HpkeCrypto for HpkeLibcrux { KemAlgorithm::DhKem25519 | KemAlgorithm::DhKemP256 | KemAlgorithm::XWingDraft06 => { Ok(()) } + #[cfg(feature = "rustcrypto-p-curves")] KemAlgorithm::DhKemP384 | KemAlgorithm::DhKemP521 => Ok(()), - #[cfg(feature = "draft-connolly-cfrg-hpke-mlkem")] - KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Ok(()), + + #[cfg(any( + feature = "draft-connolly-cfrg-hpke-mlkem", + feature = "draft-ietf-hpke-pq" + ))] + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Ok(()), + + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 => Ok(()), + + #[cfg(all(feature = "draft-ietf-hpke-pq", feature = "rustcrypto-p-curves"))] + KemAlgorithm::MlKem1024P384 => Ok(()), _ => Err(Error::UnknownKemAlgorithm), } } /// Returns an error if the AEAD algorithm is not supported by this crypto provider. - fn supports_aead(alg: AeadAlgorithm) -> Result<(), Error> { - match alg { - AeadAlgorithm::Aes128Gcm | AeadAlgorithm::Aes256Gcm => Ok(()), - AeadAlgorithm::ChaCha20Poly1305 => Ok(()), - AeadAlgorithm::HpkeExport => Ok(()), - } + fn supports_aead(_alg: AeadAlgorithm) -> Result<(), Error> { + Ok(()) } } @@ -419,7 +512,7 @@ fn dh_kem_extract_and_expand( dh: &[u8], kem_context: &[u8], ) -> Result, Error> { - let kdf_alg: KdfAlgorithm = alg.into(); + let kdf_alg = two_stage_kdf(alg)?; let suite_id = kem_suite_id(alg); let eae_prk = labeled_extract(kdf_alg, &[], &suite_id, "eae_prk", dh)?; labeled_expand( @@ -439,10 +532,18 @@ fn kem_suite_id(alg: KemAlgorithm) -> [u8; 5] { [b'K', b'E', b'M', kem_id[0], kem_id[1]] } +/// The two-stage KDF paired with a DH-based KEM. These KEMs always use an HKDF +/// KDF, so the conversion is infallible in practice. +#[cfg(feature = "rustcrypto-p-curves")] +#[inline(always)] +fn two_stage_kdf(alg: KemAlgorithm) -> Result { + TwoStageKdfAlgorithm::try_from(KdfAlgorithm::from(alg)) +} + #[cfg(feature = "rustcrypto-p-curves")] #[inline(always)] fn labeled_extract( - alg: KdfAlgorithm, + alg: TwoStageKdfAlgorithm, salt: &[u8], suite_id: &[u8], label: &str, @@ -457,7 +558,7 @@ fn labeled_extract( #[cfg(feature = "rustcrypto-p-curves")] #[inline(always)] fn labeled_expand( - alg: KdfAlgorithm, + alg: TwoStageKdfAlgorithm, prk: &[u8], suite_id: &[u8], label: &str, @@ -482,7 +583,7 @@ fn p_curve_key_gen_seed(alg: KemAlgorithm, seed: &[u8]) -> Result<[u8; 32], Erro return Err(Error::InsufficientRandomness); } - let kdf_alg: KdfAlgorithm = alg.into(); + let kdf_alg = two_stage_kdf(alg)?; let extracted = ::kdf_extract(kdf_alg, &[], seed)?; extracted[..32] .try_into() @@ -499,12 +600,29 @@ fn nist_format_uncompressed(mut pk: Vec) -> Vec { } #[inline(always)] -fn kdf_algorithm_to_libcrux_hkdf_algorithm(alg: KdfAlgorithm) -> libcrux_hkdf::Algorithm { +fn kdf_algorithm_to_libcrux_hkdf_algorithm(alg: TwoStageKdfAlgorithm) -> libcrux_hkdf::Algorithm { match alg { - KdfAlgorithm::HkdfSha256 => libcrux_hkdf::Algorithm::Sha256, - KdfAlgorithm::HkdfSha384 => libcrux_hkdf::Algorithm::Sha384, - KdfAlgorithm::HkdfSha512 => libcrux_hkdf::Algorithm::Sha512, + TwoStageKdfAlgorithm::HkdfSha256 => libcrux_hkdf::Algorithm::Sha256, + TwoStageKdfAlgorithm::HkdfSha384 => libcrux_hkdf::Algorithm::Sha384, + TwoStageKdfAlgorithm::HkdfSha512 => libcrux_hkdf::Algorithm::Sha512, + } +} + +/// `SHAKE.Derive(concat(inputs), L) = SHAKE(concat(inputs), d = 8*L)`. +/// +/// See draft-ietf-hpke-pq Section 5. +fn shake_derive(alg: SingleStageKdfAlgorithm, inputs: &[&[u8]], len: usize) -> Vec { + let ikm = concat(inputs); + let mut out = alloc::vec![0u8; len]; + match alg { + SingleStageKdfAlgorithm::Shake128 => libcrux_sha3::shake128_ema(&mut out, &ikm), + SingleStageKdfAlgorithm::Shake256 => libcrux_sha3::shake256_ema(&mut out, &ikm), + SingleStageKdfAlgorithm::TurboShake128 | SingleStageKdfAlgorithm::TurboShake256 => { + // Not supported yet + unreachable!() + } } + out } #[inline(always)] @@ -512,15 +630,69 @@ fn kem_key_type_to_libcrux_alg(alg: KemAlgorithm) -> Result Ok(libcrux_kem::Algorithm::X25519), KemAlgorithm::DhKemP256 => Ok(libcrux_kem::Algorithm::Secp256r1), - #[cfg(feature = "draft-connolly-cfrg-hpke-mlkem")] + #[cfg(any( + feature = "draft-connolly-cfrg-hpke-mlkem", + feature = "draft-ietf-hpke-pq" + ))] + KemAlgorithm::MlKem512 => Ok(libcrux_kem::Algorithm::MlKem512), + #[cfg(any( + feature = "draft-connolly-cfrg-hpke-mlkem", + feature = "draft-ietf-hpke-pq" + ))] KemAlgorithm::MlKem768 => Ok(libcrux_kem::Algorithm::MlKem768), - #[cfg(feature = "draft-connolly-cfrg-hpke-mlkem")] + #[cfg(any( + feature = "draft-connolly-cfrg-hpke-mlkem", + feature = "draft-ietf-hpke-pq" + ))] KemAlgorithm::MlKem1024 => Ok(libcrux_kem::Algorithm::MlKem1024), KemAlgorithm::XWingDraft06 => Ok(libcrux_kem::Algorithm::XWingKemDraft06), _ => Err(Error::UnknownKemAlgorithm), } } +/// The encapsulation-randomness length for the post-quantum KEMs (`N_random`), +/// or `None` for the DH-based KEMs (which inject via `Hpke::random`). Used only +/// by the deterministic test path. +#[cfg(feature = "deterministic-prng")] +#[inline] +fn pq_encaps_randomness_len(alg: KemAlgorithm) -> Option { + match alg { + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Some(32), + KemAlgorithm::XWingDraft06 => Some(64), + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 => Some(32 + 128), + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem1024P384 => Some(32 + 48), + _ => None, + } +} + +/// Derandomized encapsulation (test-only), using the supplied `randomness` as +/// the KEM's encapsulation randomness. Used by the known-answer tests so that +/// the sender-side `enc` matches the vectors. +#[cfg(feature = "deterministic-prng")] +#[inline] +fn kem_encaps_derand( + alg: KemAlgorithm, + pk_r: &[u8], + randomness: &[u8], +) -> Result<(Vec, Vec), Error> { + match alg { + #[cfg(feature = "draft-ietf-hpke-pq")] + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => { + hybrid::encaps_derand(alg, pk_r, randomness) + } + _ => { + let kem_alg = kem_key_type_to_libcrux_alg(alg)?; + let pk = libcrux_kem::PublicKey::decode(kem_alg, pk_r) + .map_err(|_| Error::KemInvalidPublicKey)?; + pk.encapsulate_derand(randomness) + .map_err(|e| Error::CryptoLibraryError(format!("Encaps error {:?}", e))) + .map(|(ss, ct)| (ss.encode(), ct.encode())) + } + } +} + #[inline(always)] fn kem_key_type_to_ecdh_alg(alg: KemAlgorithm) -> Result { match alg { @@ -540,12 +712,291 @@ fn aead_alg(alg_type: AeadAlgorithm) -> Result { } } -#[cfg(feature = "rustcrypto-p-curves")] #[inline(always)] fn concat(values: &[&[u8]]) -> Vec { values.join(&[][..]) } +/// ML-KEM/ECDH hybrid KEMs (`MLKEM768-P256`, `MLKEM1024-P384`). +/// +/// The authoritative reference is `draft-ietf-hpke-pq` (it defines the HPKE +/// integration, the code points, and the test vectors). These are the +/// `MLKEM768-P256` / `MLKEM1024-P384` instances of +/// `draft-irtf-cfrg-concrete-hybrid-kems`, built with the `CG` framework +/// (C2PRI Combiner with a nominal Group `T`) from `draft-irtf-cfrg-hybrid-kems`: +/// the combiner is `SHA3-256(ss_PQ || ss_T || ct_T || ek_T || label)`. See +/// * https://datatracker.ietf.org/doc/html/draft-ietf-hpke-pq-05 (authoritative) +/// * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-concrete-hybrid-kems-03 +/// * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hybrid-kems-09 +/// +/// * The decapsulation key is the 32-byte seed; key generation and +/// decapsulation expand it with SHAKE256 into `seed_pq (64) || seed_t` and +/// re-derive the components. +/// * Rejection-sampled scalars and uncompressed SEC1 point encodings for NIST; +/// `ss_T` is the x-coordinate of the DH result. +/// * Wire formats: `ek = ek_PQ || ek_T`, `ct = ct_PQ || ct_T`. +#[cfg(feature = "draft-ietf-hpke-pq")] +mod hybrid { + use super::*; + + /// The nominal group (traditional component) of a hybrid KEM. + #[derive(Clone, Copy)] + enum NominalGroup { + P256, + P384, + } + + /// Per-instance hybrid KEM parameters. + struct Params { + /// The ML-KEM variant. + ml_alg: libcrux_kem::Algorithm, + + /// The nominal group. + curve: NominalGroup, + + /// Domain-separation label for the combiner. + label: &'static [u8], + + /// Encoded ML-KEM encapsulation-key length. + ml_ek_len: usize, + + /// Encoded ML-KEM ciphertext length. + ml_ct_len: usize, + + /// The group's seed length (`T::SEED_SIZE`). The seed expanded by + /// [`expand`] is `64` (ML-KEM `seed_pq`) plus this. + group_seed_len: usize, + } + + #[inline] + const fn params(alg: KemAlgorithm) -> Result { + match alg { + // P-256: ML-KEM seed (64) + group seed (128 = 4 rejection windows), + // matching the P-256 nominal-group `Nseed` of 128 in + // `draft-irtf-cfrg-concrete-hybrid-kems` §3.1. The encap randomness + // is `32 + 128 = 160`, as in the `draft-ietf-hpke-pq` vectors + // (resolving https://github.com/hpkewg/hpke-pq/issues/59). + KemAlgorithm::MlKem768P256 => Ok(Params { + ml_alg: libcrux_kem::Algorithm::MlKem768, + curve: NominalGroup::P256, + label: b"MLKEM768-P256", + ml_ek_len: 1184, + ml_ct_len: 1088, + group_seed_len: 128, + }), + // P-384: ML-KEM seed (64) + group seed (48). + KemAlgorithm::MlKem1024P384 => Ok(Params { + ml_alg: libcrux_kem::Algorithm::MlKem1024, + curve: NominalGroup::P384, + label: b"MLKEM1024-P384", + ml_ek_len: 1568, + ml_ct_len: 1568, + group_seed_len: 48, + }), + _ => Err(Error::UnknownKemAlgorithm), + } + } + + /// `ss = SHA3-256(ss_PQ || ss_T || ct_T || ek_T || label)`. + #[inline] + fn combine(p: &Params, ss_pq: &[u8], ss_t: &[u8], ct_t: &[u8], ek_t: &[u8]) -> Vec { + let input = concat(&[ss_pq, ss_t, ct_t, ek_t, p.label]); + libcrux_sha3::sha256(&input).to_vec() + } + + #[inline] + fn split_at_or(data: &[u8], n: usize, err: Error) -> Result<(&[u8], &[u8]), Error> { + data.split_at_checked(n).ok_or(err) + } + + // --- Nominal group operations, matching concrete-hybrid-kems. --- + // + // The traditional component of each hybrid is just the corresponding + // `DhKem*` group, so these delegate to the provider's own ECDH / key + // derivation paths (`dh_validate_sk`, `secret_to_public`, `dh`) rather than + // re-implementing scalar validation, point derivation, and ECDH. + + #[inline] + fn scalar_size(curve: NominalGroup) -> usize { + match curve { + NominalGroup::P256 => 32, + NominalGroup::P384 => 48, + } + } + + /// The `DhKem*` code point for `curve`'s nominal group. + #[inline] + fn curve_kem_alg(curve: NominalGroup) -> KemAlgorithm { + match curve { + NominalGroup::P256 => KemAlgorithm::DhKemP256, + NominalGroup::P384 => KemAlgorithm::DhKemP384, + } + } + + /// Return the canonical scalar bytes if `bytes` is a valid non-zero scalar. + #[inline] + fn validate_scalar(curve: NominalGroup, bytes: &[u8]) -> Option> { + ::dh_validate_sk(curve_kem_alg(curve), bytes).ok() + } + + /// `random_scalar`: rejection-sample successive `SCALAR_SIZE` windows, + /// returning the first valid scalar. Bounded by `seed.len() / SCALAR_SIZE` + /// windows; the trailing partial window (if any) is ignored, matching + /// concrete-hybrid-kems. + #[inline] + fn random_scalar(curve: NominalGroup, seed: &[u8]) -> Result, Error> { + seed.chunks_exact(scalar_size(curve)) + .find_map(|window| validate_scalar(curve, window)) + .ok_or(Error::KemInvalidSecretKey) + } + + /// `exp(generator, scalar)` — the public key, uncompressed SEC1. + #[inline] + fn base_pub(curve: NominalGroup, scalar: &[u8]) -> Result, Error> { + ::secret_to_public(curve_kem_alg(curve), scalar) + } + + /// `element_to_shared_secret(exp(peer, scalar))` — the DH x-coordinate. + #[inline] + fn ecdh(curve: NominalGroup, scalar: &[u8], peer: &[u8]) -> Result, Error> { + ::dh(curve_kem_alg(curve), peer, scalar) + } + + /// The component key material expanded from a hybrid seed. + struct Expanded { + ek_pq: Vec, + seed_pq: [u8; 64], + dk_t: Vec, + ek_t: Vec, + } + + /// Expand the seed into the component encapsulation key (`ek_PQ || ek_T`), + /// the ML-KEM `seed_pq`, and the group scalar `dk_T`. + #[inline] + fn expand(p: &Params, seed: &[u8]) -> Result { + let material = shake_derive( + SingleStageKdfAlgorithm::Shake256, + &[seed], + 64 + p.group_seed_len, + ); + let mut seed_pq = [0u8; 64]; + seed_pq.copy_from_slice(&material[..64]); + let seed_t = &material[64..]; + + let (_dk_pq, ek_pq) = libcrux_kem::key_gen_derand(p.ml_alg, &seed_pq) + .map(|(sk, pk)| (sk, pk.encode())) + .map_err(|e| Error::CryptoLibraryError(format!("KEM key gen error: {:?}", e)))?; + + let dk_t = random_scalar(p.curve, seed_t)?; + let ek_t = base_pub(p.curve, &dk_t)?; + Ok(Expanded { + ek_pq, + seed_pq, + dk_t, + ek_t, + }) + } + + #[inline] + pub(super) fn key_gen( + alg: KemAlgorithm, + prng: &mut HpkeLibcruxPrng, + ) -> Result<(Vec, Vec), Error> { + let mut seed = alloc::vec![0u8; 32]; + prng.try_fill_bytes(&mut seed) + .map_err(|_| Error::InsufficientRandomness)?; + key_gen_derand(alg, &seed) + } + + #[inline] + /// Deterministic key generation: the 32-byte seed is the decapsulation key. + pub(super) fn key_gen_derand( + alg: KemAlgorithm, + seed: &[u8], + ) -> Result<(Vec, Vec), Error> { + let p = params(alg)?; + let e = expand(&p, seed)?; + Ok((concat(&[&e.ek_pq, &e.ek_t]), seed.to_vec())) + } + + #[inline] + pub(super) fn encaps( + alg: KemAlgorithm, + pk_r: &[u8], + prng: &mut HpkeLibcruxPrng, + ) -> Result<(Vec, Vec), Error> { + let p = params(alg)?; + let (ek_pq, ek_t) = split_at_or(pk_r, p.ml_ek_len, Error::KemInvalidPublicKey)?; + + // Post-quantum encapsulation (draws ML-KEM randomness first). + let ml_pk = libcrux_kem::PublicKey::decode(p.ml_alg, ek_pq) + .map_err(|_| Error::KemInvalidPublicKey)?; + let (ss_pq, ct_pq) = ml_pk + .encapsulate(prng) + .map(|(ss, ct)| (ss.encode(), ct.encode())) + .map_err(|e| Error::CryptoLibraryError(format!("Encaps error {:?}", e)))?; + + // Traditional encapsulation: ephemeral scalar from `T::SEED_SIZE` bytes. + let mut seed_e = alloc::vec![0u8; p.group_seed_len]; + prng.try_fill_bytes(&mut seed_e) + .map_err(|_| Error::InsufficientRandomness)?; + let sk_e = random_scalar(p.curve, &seed_e)?; + let ct_t = base_pub(p.curve, &sk_e)?; + let ss_t = ecdh(p.curve, &sk_e, ek_t)?; + + let ss = combine(&p, &ss_pq, &ss_t, &ct_t, ek_t); + Ok((ss, concat(&[&ct_pq, &ct_t]))) + } + + /// Derandomized encapsulation: `randomness = randomness_PQ (32) || seed_T`. + #[cfg(feature = "deterministic-prng")] + #[inline] + pub(super) fn encaps_derand( + alg: KemAlgorithm, + pk_r: &[u8], + randomness: &[u8], + ) -> Result<(Vec, Vec), Error> { + let p = params(alg)?; + let (ek_pq, ek_t) = split_at_or(pk_r, p.ml_ek_len, Error::KemInvalidPublicKey)?; + let (rand_pq, seed_e) = split_at_or(randomness, 32, Error::InsufficientRandomness)?; + + let ml_pk = libcrux_kem::PublicKey::decode(p.ml_alg, ek_pq) + .map_err(|_| Error::KemInvalidPublicKey)?; + let (ss_pq, ct_pq) = ml_pk + .encapsulate_derand(rand_pq) + .map(|(ss, ct)| (ss.encode(), ct.encode())) + .map_err(|e| Error::CryptoLibraryError(format!("Encaps error {:?}", e)))?; + + let sk_e = random_scalar(p.curve, seed_e)?; + let ct_t = base_pub(p.curve, &sk_e)?; + let ss_t = ecdh(p.curve, &sk_e, ek_t)?; + + let ss = combine(&p, &ss_pq, &ss_t, &ct_t, ek_t); + Ok((ss, concat(&[&ct_pq, &ct_t]))) + } + + #[inline] + pub(super) fn decaps(alg: KemAlgorithm, ct: &[u8], sk_r: &[u8]) -> Result, Error> { + let p = params(alg)?; + let (ct_pq, ct_t) = split_at_or(ct, p.ml_ct_len, Error::KemInvalidCiphertext)?; + + // Re-expand the seed (`sk_r`) into the component keys. + let e = expand(&p, sk_r)?; + + let (dk_pq, _ek_pq) = libcrux_kem::key_gen_derand(p.ml_alg, &e.seed_pq) + .map_err(|err| Error::CryptoLibraryError(format!("KEM key gen error: {:?}", err)))?; + let ct_pq = + libcrux_kem::Ct::decode(p.ml_alg, ct_pq).map_err(|_| Error::KemInvalidCiphertext)?; + let ss_pq = ct_pq + .decapsulate(&dk_pq) + .map(|ss| ss.encode()) + .map_err(|err| Error::CryptoLibraryError(format!("Decaps error {:?}", err)))?; + + let ss_t = ecdh(p.curve, &e.dk_t, ct_t)?; + Ok(combine(&p, &ss_pq, &ss_t, ct_t, &e.ek_t)) + } +} + impl TryCryptoRng for HpkeLibcruxPrng {} impl TryRng for HpkeLibcruxPrng { diff --git a/crates/protocols/hpke/rust_crypto_provider/benches/bench_hkdf.rs b/crates/protocols/hpke/rust_crypto_provider/benches/bench_hkdf.rs index 2ea04599e..fd0105d77 100644 --- a/crates/protocols/hpke/rust_crypto_provider/benches/bench_hkdf.rs +++ b/crates/protocols/hpke/rust_crypto_provider/benches/bench_hkdf.rs @@ -14,7 +14,7 @@ fn criterion_benchmark(c: &mut Criterion) { (salt.clone(), ikm.clone()) }, |(salt, ikm)| { - let _ = HpkeRustCrypto::kdf_extract(KdfAlgorithm::HkdfSha256, &salt, &ikm); + let _ = HpkeRustCrypto::kdf_extract(TwoStageKdfAlgorithm::HkdfSha256, &salt, &ikm); }, BatchSize::SmallInput, ) @@ -29,7 +29,8 @@ fn criterion_benchmark(c: &mut Criterion) { (prk.clone(), info.clone()) }, |(prk, info)| { - let _ = HpkeRustCrypto::kdf_expand(KdfAlgorithm::HkdfSha256, &prk, &info, 32); + let _ = + HpkeRustCrypto::kdf_expand(TwoStageKdfAlgorithm::HkdfSha256, &prk, &info, 32); }, BatchSize::SmallInput, ) diff --git a/crates/protocols/hpke/rust_crypto_provider/src/lib.rs b/crates/protocols/hpke/rust_crypto_provider/src/lib.rs index 451589da8..fb18abfbd 100644 --- a/crates/protocols/hpke/rust_crypto_provider/src/lib.rs +++ b/crates/protocols/hpke/rust_crypto_provider/src/lib.rs @@ -11,7 +11,9 @@ use zeroize::Zeroize; use hpke_rs_crypto::{ error::Error, - types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}, + types::{ + AeadAlgorithm, KdfAlgorithm, KemAlgorithm, SingleStageKdfAlgorithm, TwoStageKdfAlgorithm, + }, HpkeCrypto, HpkeTestRng, }; use p256::{ @@ -62,27 +64,33 @@ impl HpkeCrypto for HpkeRustCrypto { "RustCrypto".into() } - fn kdf_extract(alg: KdfAlgorithm, salt: &[u8], ikm: &[u8]) -> Result, Error> { + fn kdf_extract(alg: TwoStageKdfAlgorithm, salt: &[u8], ikm: &[u8]) -> Result, Error> { Ok(match alg { - KdfAlgorithm::HkdfSha256 => sha256_extract(salt, ikm), - KdfAlgorithm::HkdfSha384 => sha384_extract(salt, ikm), - KdfAlgorithm::HkdfSha512 => sha512_extract(salt, ikm), + TwoStageKdfAlgorithm::HkdfSha256 => sha256_extract(salt, ikm), + TwoStageKdfAlgorithm::HkdfSha384 => sha384_extract(salt, ikm), + TwoStageKdfAlgorithm::HkdfSha512 => sha512_extract(salt, ikm), }) } fn kdf_expand( - alg: KdfAlgorithm, + alg: TwoStageKdfAlgorithm, prk: &[u8], info: &[u8], output_size: usize, ) -> Result, Error> { match alg { - KdfAlgorithm::HkdfSha256 => sha256_expand(prk, info, output_size), - KdfAlgorithm::HkdfSha384 => sha384_expand(prk, info, output_size), - KdfAlgorithm::HkdfSha512 => sha512_expand(prk, info, output_size), + TwoStageKdfAlgorithm::HkdfSha256 => sha256_expand(prk, info, output_size), + TwoStageKdfAlgorithm::HkdfSha384 => sha384_expand(prk, info, output_size), + TwoStageKdfAlgorithm::HkdfSha512 => sha512_expand(prk, info, output_size), } } + fn kdf_derive(_alg: SingleStageKdfAlgorithm, _ikm: &[u8], _l: usize) -> Result, Error> { + // The RustCrypto provider does not implement the single-stage (SHAKE) + // KDFs of draft-ietf-hpke-pq. + Err(Error::UnknownKdfAlgorithm) + } + fn dh(alg: KemAlgorithm, pk: &[u8], sk: &[u8]) -> Result, Error> { use subtle::ConstantTimeEq; match alg { @@ -307,8 +315,17 @@ impl HpkeCrypto for HpkeRustCrypto { } /// Returns an error if the KDF algorithm is not supported by this crypto provider. - fn supports_kdf(_: KdfAlgorithm) -> Result<(), Error> { - Ok(()) + fn supports_kdf(alg: KdfAlgorithm) -> Result<(), Error> { + match alg { + KdfAlgorithm::HkdfSha256 | KdfAlgorithm::HkdfSha384 | KdfAlgorithm::HkdfSha512 => { + Ok(()) + } + // The SHAKE KDFs (draft-ietf-hpke-pq) are not supported here yet. + KdfAlgorithm::TurboShake128 + | KdfAlgorithm::TurboShake256 + | KdfAlgorithm::Shake128 + | KdfAlgorithm::Shake256 => Err(Error::UnknownKdfAlgorithm), + } } /// Returns an error if the KEM algorithm is not supported by this crypto provider. diff --git a/crates/protocols/hpke/src/dh_kem.rs b/crates/protocols/hpke/src/dh_kem.rs index 9891b0de6..a1e8aadba 100644 --- a/crates/protocols/hpke/src/dh_kem.rs +++ b/crates/protocols/hpke/src/dh_kem.rs @@ -2,7 +2,11 @@ use alloc::{string::ToString, vec::Vec}; -use hpke_rs_crypto::{error::Error, types::KemAlgorithm, HpkeCrypto}; +use hpke_rs_crypto::{ + error::Error, + types::{KdfAlgorithm, KemAlgorithm, TwoStageKdfAlgorithm}, + HpkeCrypto, +}; use crate::util::*; use crate::{ @@ -10,15 +14,23 @@ use crate::{ kem::*, }; +/// The two-stage KDF for a DH-based KEM. DH KEMs always pair with an HKDF KDF, +/// so the conversion is infallible in practice. +#[inline] +fn dh_kdf(alg: KemAlgorithm) -> Result { + TwoStageKdfAlgorithm::try_from(KdfAlgorithm::from(alg)) +} + fn extract_and_expand( alg: KemAlgorithm, pk: PublicKey, kem_context: &[u8], suite_id: &[u8], ) -> Result, Error> { - let prk = labeled_extract::(alg.into(), &[], suite_id, "eae_prk", &pk)?; + let kdf = dh_kdf(alg)?; + let prk = labeled_extract::(kdf, &[], suite_id, "eae_prk", &pk)?; labeled_expand::( - alg.into(), + kdf, &prk, suite_id, "shared_secret", @@ -55,11 +67,12 @@ pub(super) fn derive_key_pair( suite_id: &[u8], ikm: &[u8], ) -> Result<(PublicKey, PrivateKey), Error> { - let dkp_prk = labeled_extract::(alg.into(), &[], suite_id, "dkp_prk", ikm)?; + let kdf = dh_kdf(alg)?; + let dkp_prk = labeled_extract::(kdf, &[], suite_id, "dkp_prk", ikm)?; let sk = match alg { KemAlgorithm::DhKem25519 => PrivateKey(labeled_expand::( - alg.into(), + kdf, &dkp_prk, suite_id, "sk", @@ -81,7 +94,7 @@ pub(super) fn derive_key_pair( // the loop will always terminate. loop { let candidate = labeled_expand::( - alg.into(), + kdf, &dkp_prk, suite_id, "candidate", diff --git a/crates/protocols/hpke/src/kdf.rs b/crates/protocols/hpke/src/kdf.rs index f941c6028..57798ae78 100644 --- a/crates/protocols/hpke/src/kdf.rs +++ b/crates/protocols/hpke/src/kdf.rs @@ -1,13 +1,54 @@ use alloc::vec::Vec; -use hpke_rs_crypto::{error::Error, types::KdfAlgorithm, HpkeCrypto}; +use hpke_rs_crypto::{ + error::Error, + types::{SingleStageKdfAlgorithm, TwoStageKdfAlgorithm}, + HpkeCrypto, +}; use crate::util::concat; -const HPKE_VERSION: &[u8] = b"HPKE-v1"; +pub(crate) const HPKE_VERSION: &[u8] = b"HPKE-v1"; +#[inline] +/// requires: x.len() <= u16::MAX. +pub(crate) fn length_prefixed(x: &[u8]) -> Vec { + concat(&[&(x.len() as u16).to_be_bytes(), x]) +} + +#[inline] +/// `LabeledDerive` for single-stage KDFs (draft-ietf-hpke-pq): +/// `Derive(ikm ‖ "HPKE-v1" ‖ suite_id ‖ I2OSP(len(label),2) ‖ label ‖ I2OSP(L,2) ‖ context, L)`. +/// +/// `Derive(input, L)` is computed by the provider's single-stage `kdf_derive`, +/// which is `SHAKE(input, 8*L)` for the SHAKE KDFs. +pub(crate) fn labeled_derive( + alg: SingleStageKdfAlgorithm, + suite_id: &[u8], + ikm: &[u8], + label: &str, + context: &[u8], + len: usize, +) -> Result, Error> { + if len > u16::MAX.into() { + return Err(Error::HpkeInvalidOutputLength); + } + + // `ikm ‖ "HPKE-v1" ‖ suite_id ‖ I2OSP(len(label),2) ‖ label ‖ I2OSP(L,2) ‖ context`. + let labeled_ikm = concat(&[ + ikm, + HPKE_VERSION, + suite_id, + &length_prefixed(label.as_bytes()), + &(len as u16).to_be_bytes(), + context, + ]); + Crypto::kdf_derive(alg, &labeled_ikm, len) +} + +#[inline] pub(crate) fn labeled_extract( - alg: KdfAlgorithm, + alg: TwoStageKdfAlgorithm, salt: &[u8], suite_id: &[u8], label: &str, @@ -17,8 +58,9 @@ pub(crate) fn labeled_extract( Crypto::kdf_extract(alg, salt, &labeled_ikm) } +#[inline] pub(crate) fn labeled_expand( - alg: KdfAlgorithm, + alg: TwoStageKdfAlgorithm, prk: &[u8], suite_id: &[u8], label: &'static str, diff --git a/crates/protocols/hpke/src/kem.rs b/crates/protocols/hpke/src/kem.rs index b1c996ed9..e5904d8bc 100644 --- a/crates/protocols/hpke/src/kem.rs +++ b/crates/protocols/hpke/src/kem.rs @@ -35,8 +35,11 @@ pub(crate) fn encaps( #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete + | KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 - | KemAlgorithm::MlKem1024 => Crypto::kem_encaps(alg, pk_r, hpke.rng()), + | KemAlgorithm::MlKem1024 + | KemAlgorithm::MlKem768P256 + | KemAlgorithm::MlKem1024P384 => Crypto::kem_encaps(alg, pk_r, hpke.rng()), } } @@ -55,8 +58,11 @@ pub(crate) fn decaps( #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete + | KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 - | KemAlgorithm::MlKem1024 => Crypto::kem_decaps(alg, enc, sk_r), + | KemAlgorithm::MlKem1024 + | KemAlgorithm::MlKem768P256 + | KemAlgorithm::MlKem1024P384 => Crypto::kem_decaps(alg, enc, sk_r), } } @@ -81,8 +87,11 @@ pub(crate) fn auth_encaps( #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete + | KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 - | KemAlgorithm::MlKem1024 => Err(Error::UnsupportedKemOperation), + | KemAlgorithm::MlKem1024 + | KemAlgorithm::MlKem768P256 + | KemAlgorithm::MlKem1024P384 => Err(Error::UnsupportedKemOperation), } } @@ -104,8 +113,11 @@ pub(crate) fn auth_decaps( #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete + | KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 - | KemAlgorithm::MlKem1024 => Err(Error::UnsupportedKemOperation), + | KemAlgorithm::MlKem1024 + | KemAlgorithm::MlKem768P256 + | KemAlgorithm::MlKem1024P384 => Err(Error::UnsupportedKemOperation), } } @@ -125,8 +137,11 @@ pub(crate) fn key_gen( #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete + | KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 - | KemAlgorithm::MlKem1024 => { + | KemAlgorithm::MlKem1024 + | KemAlgorithm::MlKem768P256 + | KemAlgorithm::MlKem1024P384 => { let mut seed = vec![0u8; alg.private_key_len()]; prng.fill_bytes(&mut seed); let (pk, sk) = derive_key_pair::(alg, &seed)?; @@ -151,12 +166,51 @@ pub(crate) fn derive_key_pair( | KemAlgorithm::DhKem448 => dh_kem::derive_key_pair::(alg, &ciphersuite(alg), ikm), #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete => { - let seed = libcrux_sha3::shake256::<32>(ikm); + let seed = pq_derive_keypair_seed(alg, ikm, 32)?; Crypto::kem_key_gen_derand(alg, &seed).map(|(ek, dk)| (ek, PrivateKey(dk))) } - KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { - let seed = libcrux_sha3::shake256::<64>(ikm); + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { + let seed = pq_derive_keypair_seed(alg, ikm, 64)?; + Crypto::kem_key_gen_derand(alg, &seed).map(|(ek, dk)| (ek, PrivateKey(dk))) + } + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => { + // The hybrid `DeriveKeyPair` produces a 32-byte seed that the + // crypto provider expands into the component key material. + let seed = pq_derive_keypair_seed(alg, ikm, 32)?; Crypto::kem_key_gen_derand(alg, &seed).map(|(ek, dk)| (ek, PrivateKey(dk))) } } } + +/// Derive the `DeriveKeyPair` seed of length `len` for a post-quantum KEM. +/// +/// This is `SHAKE256.LabeledDerive(ikm, "DeriveKeyPair", "", len)` with the KEM +/// `suite_id` (`"KEM" || kem_id`), as specified by `draft-ietf-hpke-pq`. The +/// deprecated `draft-connolly-cfrg-hpke-mlkem` instead uses the unlabeled +/// `SHAKE256(ikm, len)`. Either way SHAKE256 is fixed by the KEM (independent of +/// the HPKE KDF), so it is computed directly with `libcrux_sha3` rather than via +/// the crypto provider's KDF — that way it works for any provider whose +/// ML-KEM/X-Wing KEMs route through here. +fn pq_derive_keypair_seed(alg: KemAlgorithm, ikm: &[u8], len: usize) -> Result, Error> { + #[cfg(feature = "draft-connolly-cfrg-hpke-mlkem")] + let _ = alg; + + // The deprecated `draft-connolly-cfrg-hpke-mlkem` derives the seed from the + // unlabeled `ikm`; every other (current) configuration labels it first with the + // `SHAKE256.LabeledDerive(ikm, "DeriveKeyPair", "", len)` input: + // `ikm ‖ "HPKE-v1" ‖ suite_id ‖ I2OSP(len(label),2) ‖ label ‖ I2OSP(L,2)`. + #[cfg(not(feature = "draft-connolly-cfrg-hpke-mlkem"))] + let ikm = &util::concat(&[ + ikm, + crate::kdf::HPKE_VERSION, + &ciphersuite(alg), + &crate::kdf::length_prefixed(b"DeriveKeyPair"), + &(len as u16).to_be_bytes(), + ]); + + Ok(match len { + 32 => libcrux_sha3::shake256::<32>(ikm).to_vec(), + 64 => libcrux_sha3::shake256::<64>(ikm).to_vec(), + _ => return Err(Error::InsufficientRandomness), + }) +} diff --git a/crates/protocols/hpke/src/lib.rs b/crates/protocols/hpke/src/lib.rs index 59efaf99e..8364afa0c 100644 --- a/crates/protocols/hpke/src/lib.rs +++ b/crates/protocols/hpke/src/lib.rs @@ -114,10 +114,12 @@ use hpke_rs_crypto::HpkeTestRng; #[cfg(not(feature = "hpke-test-prng"))] use hpke_rs_crypto::TryRng; use hpke_rs_crypto::{ - types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}, + types::{ + AeadAlgorithm, KdfAlgorithm, KemAlgorithm, SingleStageKdfAlgorithm, TwoStageKdfAlgorithm, + }, HpkeCrypto, }; -use prelude::kdf::{labeled_expand, labeled_extract}; +use prelude::kdf::{labeled_derive, labeled_expand, labeled_extract, length_prefixed}; /// Re-export of the HPKE types from the [`hpke_rs_crypto`] crate. pub use hpke_rs_crypto::types as hpke_types; @@ -407,11 +409,25 @@ impl Context { /// return LabeledExpand(self.exporter_secret, "sec", exporter_context, L) ///``` pub fn export(&self, exporter_context: &[u8], length: usize) -> Result, HpkeError> { + const LABEL: &str = "sec"; + + if let Ok(kdf) = SingleStageKdfAlgorithm::try_from(self.hpke.kdf_id) { + return labeled_derive::( + kdf, + &self.hpke.ciphersuite(), + &self.exporter_secret, + LABEL, + exporter_context, + length, + ) + .map_err(|e| HpkeError::CryptoError(format!("Crypto error: {}", e))); + } + labeled_expand::( - self.hpke.kdf_id, + self.hpke.two_stage_kdf()?, &self.exporter_secret, &self.hpke.ciphersuite(), - "sec", + LABEL, exporter_context, length, ) @@ -741,16 +757,23 @@ impl Hpke { ]) } + /// The two-stage KDF for this ciphersuite, or an error if it is single-stage. + #[inline] + fn two_stage_kdf(&self) -> Result { + TwoStageKdfAlgorithm::try_from(self.kdf_id) + .map_err(|_| HpkeError::CryptoError("Unsupported KDF".to_string())) + } + #[inline] fn key_schedule_context( &self, + kdf: TwoStageKdfAlgorithm, info: &[u8], psk_id: &[u8], suite_id: &[u8], ) -> Result, HpkeError> { - let psk_id_hash = - labeled_extract::(self.kdf_id, &[0], suite_id, "psk_id_hash", psk_id)?; - let info_hash = labeled_extract::(self.kdf_id, &[0], suite_id, "info_hash", info)?; + let psk_id_hash = labeled_extract::(kdf, &[0], suite_id, "psk_id_hash", psk_id)?; + let info_hash = labeled_extract::(kdf, &[0], suite_id, "info_hash", info)?; Ok(util::concat(&[ &[self.mode as u8], &psk_id_hash, @@ -769,13 +792,55 @@ impl Hpke { ) -> Result, HpkeError> { self.verify_psk_inputs(psk, psk_id)?; let suite_id = self.ciphersuite(); - let key_schedule_context = self.key_schedule_context(info, psk_id, &suite_id)?; - let secret = - labeled_extract::(self.kdf_id, shared_secret, &suite_id, "secret", psk) - .map_err(|e| HpkeError::CryptoError(format!("Crypto error: {}", e)))?; + + // Single-stage (SHAKE) KDFs use a different key-schedule shape: a single + // `LabeledDerive` producing key ‖ base_nonce ‖ exporter_secret, with the + // PSK/info length-prefixed. See draft-ietf-hpke-pq. + if let Ok(kdf) = SingleStageKdfAlgorithm::try_from(self.kdf_id) { + let nk = Crypto::aead_key_length(self.aead_id); + let nn = Crypto::aead_nonce_length(self.aead_id); + let nh = Crypto::kdf_digest_length(self.kdf_id); + + // Every value below is emitted with a 2-byte length prefix; reject + // anything that would not fit rather than silently truncating it. + for field in [psk, shared_secret, psk_id, info] { + if field.len() > u16::MAX as usize { + return Err(HpkeError::InvalidInput); + } + } + + let secrets = util::concat(&[&length_prefixed(psk), &length_prefixed(shared_secret)]); + let context = util::concat(&[ + &[self.mode as u8], + &length_prefixed(psk_id), + &length_prefixed(info), + ]); + let secret = labeled_derive::( + kdf, + &suite_id, + &secrets, + "secret", + &context, + nk + nn + nh, + ) + .map_err(|e| HpkeError::CryptoError(format!("Crypto error: {}", e)))?; + + return Ok(Context { + key: secret[..nk].to_vec(), + nonce: secret[nk..nk + nn].to_vec(), + exporter_secret: secret[nk + nn..].to_vec(), + sequence_number: 0, + hpke: self.clone(), + }); + } + + let kdf = self.two_stage_kdf()?; + let key_schedule_context = self.key_schedule_context(kdf, info, psk_id, &suite_id)?; + let secret = labeled_extract::(kdf, shared_secret, &suite_id, "secret", psk) + .map_err(|e| HpkeError::CryptoError(format!("Crypto error: {}", e)))?; let key = labeled_expand::( - self.kdf_id, + kdf, &secret, &suite_id, "key", @@ -784,7 +849,7 @@ impl Hpke { ) .map_err(|e| HpkeError::CryptoError(format!("Crypto error: {}", e)))?; let base_nonce = labeled_expand::( - self.kdf_id, + kdf, &secret, &suite_id, "base_nonce", @@ -793,7 +858,7 @@ impl Hpke { ) .map_err(|e| HpkeError::CryptoError(format!("Crypto error: {}", e)))?; let exporter_secret = labeled_expand::( - self.kdf_id, + kdf, &secret, &suite_id, "exp", diff --git a/crates/protocols/hpke/src/test_kdf.rs b/crates/protocols/hpke/src/test_kdf.rs index 8ae969d7a..ae059ba2b 100644 --- a/crates/protocols/hpke/src/test_kdf.rs +++ b/crates/protocols/hpke/src/test_kdf.rs @@ -1,4 +1,4 @@ -use hpke_rs_crypto::{types::KdfAlgorithm, HpkeCrypto}; +use hpke_rs_crypto::{types::TwoStageKdfAlgorithm, HpkeCrypto}; use hpke_rs_libcrux::HpkeLibcrux; use crate::test_util::hex_to_bytes; @@ -16,9 +16,9 @@ fn test_hkdf_sha256() { "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", ); - let prk = HpkeLibcrux::kdf_extract(KdfAlgorithm::HkdfSha256, &salt, &ikm) + let prk = HpkeLibcrux::kdf_extract(TwoStageKdfAlgorithm::HkdfSha256, &salt, &ikm) .expect("Error extracting with HKDF"); - let okm = HpkeLibcrux::kdf_expand(KdfAlgorithm::HkdfSha256, &prk, &info, len) + let okm = HpkeLibcrux::kdf_expand(TwoStageKdfAlgorithm::HkdfSha256, &prk, &info, len) .expect("Error expanding with HKDF"); assert_eq!(&expected_prk, &prk); diff --git a/crates/protocols/hpke/tests/test_hpke_kat.rs b/crates/protocols/hpke/tests/test_hpke_kat.rs index ae979391a..15f78f267 100644 --- a/crates/protocols/hpke/tests/test_hpke_kat.rs +++ b/crates/protocols/hpke/tests/test_hpke_kat.rs @@ -6,7 +6,6 @@ use serde::{self, Deserialize, Serialize}; use std::convert::TryInto; use std::fs::File; use std::io::BufReader; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Instant; use hpke::prelude::*; @@ -33,6 +32,8 @@ struct HpkeTestVector { ikmE: String, skRm: String, skSm: Option, + // Ephemeral key material is absent from the post-quantum vectors (the + // encapsulation randomness is not expressed as an ephemeral key pair there). skEm: Option, psk: Option, psk_id: Option, @@ -69,292 +70,321 @@ struct ExportsKAT { } /// Run the known-answer tests for all `tests` supported by the `Crypto` backend, -/// and return the number of test vectors that were actually executed -/// (i.e. not skipped because the backend doesn't support the ciphersuite). -fn kat(tests: Vec) -> usize { - let executed = AtomicUsize::new(0); +/// and return the `KemAlgorithm` of every vector that was actually executed +/// (vectors skipped because the backend doesn't support the ciphersuite yield +/// `None`). The caller uses this to assert exactly which suites ran. +fn kat(tests: Vec) -> Vec { // Replace into_par_iter() with into_iter() to run tests sequentially. - tests.into_par_iter().for_each(|test| { - println!( - "Testing mode {:?} with ciphersuite {:?}_{:?}_{:?}", - test.mode, test.kem_id, test.kdf_id, test.aead_id - ); - let mode: HpkeMode = test.mode.try_into().unwrap(); - let kem_id: KemAlgorithm = test.kem_id.try_into().unwrap(); - let kdf_id: KdfAlgorithm = test.kdf_id.try_into().unwrap(); - let aead_id: AeadAlgorithm = test.aead_id.try_into().unwrap(); - - if Crypto::supports_kem(kem_id).is_err() { - log::trace!( - " > KEM {:?} not implemented yet for {}", - kem_id, - Crypto::name() + tests + .into_par_iter() + .filter_map(|test| { + println!( + "Testing mode {:?} with ciphersuite {:?}_{:?}_{:?}", + test.mode, test.kem_id, test.kdf_id, test.aead_id ); - return; - } + let mode: HpkeMode = test.mode.try_into().unwrap(); + // Algorithm identifiers this build doesn't know are simply skipped. + let (Ok(kem_id), Ok(kdf_id), Ok(aead_id)): ( + Result, + Result, + Result, + ) = ( + test.kem_id.try_into(), + test.kdf_id.try_into(), + test.aead_id.try_into(), + ) else { + return None; + }; + + if Crypto::supports_kem(kem_id).is_err() { + log::trace!( + " > KEM {:?} not implemented yet for {}", + kem_id, + Crypto::name() + ); + return None; + } - if Crypto::supports_aead(aead_id).is_err() { - log::trace!( - " > AEAD {:?} not implemented yet for {}", - aead_id, - Crypto::name() - ); - return; - } + // All KDFs and AEADs are supported (when the KEM is supported). + assert!(Crypto::supports_aead(aead_id).is_ok()); + assert!(Crypto::supports_kdf(kdf_id).is_ok()); - if Crypto::supports_kdf(kdf_id).is_err() { log::trace!( - " > KDF {:?} not implemented yet for {}", + "Testing mode {:?} with ciphersuite {:?}_{:?}_{:?}", + mode, + kem_id, kdf_id, - Crypto::name() + aead_id ); - return; - } - - log::trace!( - "Testing mode {:?} with ciphersuite {:?}_{:?}_{:?}", - mode, - kem_id, - kdf_id, - aead_id - ); - // This vector is supported and will run all its assertions below. - executed.fetch_add(1, Ordering::Relaxed); + // Init HPKE with the given mode and ciphersuite. + let mut hpke = Hpke::::new(mode, kem_id, kdf_id, aead_id); + + // Set up sender and receiver. + let pk_rm = HpkePublicKey::new(hex_to_bytes(&test.pkRm)); + let sk_rm = HpkePrivateKey::new(hex_to_bytes(&test.skRm)); + // Ephemeral key pair is only present in the classical (RFC 9180) vectors. + let ephemeral_keys = match (&test.pkEm, &test.skEm) { + (Some(pk), Some(sk)) => Some(( + HpkePublicKey::new(hex_to_bytes(pk)), + HpkePrivateKey::new(hex_to_bytes(sk)), + )), + _ => None, + }; + let pk_sm = hex_to_bytes_option(test.pkSm); + let pk_sm = if pk_sm.is_empty() { + None + } else { + Some(HpkePublicKey::new(pk_sm)) + }; + let pk_sm = pk_sm.as_ref(); + let sk_sm = hex_to_bytes_option(test.skSm); + let sk_sm = if sk_sm.is_empty() { + None + } else { + Some(HpkePrivateKey::new(sk_sm)) + }; + let sk_sm = sk_sm.as_ref(); + let info = hex_to_bytes(&test.info); + let psk = hex_to_bytes_option(test.psk); + let psk = vec_to_option_slice(&psk); + let psk_id = hex_to_bytes_option(test.psk_id); + let psk_id = vec_to_option_slice(&psk_id); + let shared_secret = hex_to_bytes(&test.shared_secret); + let key = hex_to_bytes(&test.key); + let nonce = hex_to_bytes(&test.base_nonce); + let exporter_secret = hex_to_bytes(&test.exporter_secret); + + // Input key material. + let ikm_r = hex_to_bytes(&test.ikmR); + let ikm_e = hex_to_bytes(&test.ikmE); + let ikm_s = hex_to_bytes_option(test.ikmS); + + // Use internal `key_schedule` function for KAT. + let mut direct_ctx = hpke + .key_schedule( + &shared_secret, + &info, + psk.unwrap_or_default(), + psk_id.unwrap_or_default(), + ) + .unwrap_or_else(|e| { + panic!("key_schedule failed for {kem_id:?}_{kdf_id:?}_{aead_id:?}: {e:?}") + }); + + // Check setup info + // Note that key and nonce are empty for exporter only key derivation. + assert_eq!(direct_ctx.key(), key); + assert_eq!(direct_ctx.nonce(), nonce); + assert_eq!(direct_ctx.exporter_secret(), exporter_secret); + assert_eq!(direct_ctx.sequence_number(), 0); + + // Test key pair derivation. + let (my_sk_r, my_pk_r) = hpke.derive_key_pair(&ikm_r).unwrap().into_keys(); + assert_eq!(sk_rm, my_sk_r); + assert_eq!(pk_rm, my_pk_r); + if let Some((pk_em, sk_em)) = &ephemeral_keys { + let (my_sk_e, my_pk_e) = hpke.derive_key_pair(&ikm_e).unwrap().into_keys(); + assert_eq!(sk_em, &my_sk_e); + assert_eq!(pk_em, &my_pk_e); + } + if let (Some(sk_sm), Some(pk_sm)) = (sk_sm, pk_sm) { + let (my_sk_s, my_pk_s) = hpke.derive_key_pair(&ikm_s).unwrap().into_keys(); + assert_eq!(sk_sm, &my_sk_s); + assert_eq!(pk_sm, &my_pk_s); + } - // Init HPKE with the given mode and ciphersuite. - let mut hpke = Hpke::::new(mode, kem_id, kdf_id, aead_id); + // Setup KAT receiver. + let kat_enc = hex_to_bytes(&test.enc); + let mut receiver_context_kat = hpke + .setup_receiver(&kat_enc, &sk_rm, &info, psk, psk_id, pk_sm) + .unwrap(); - // Set up sender and receiver. - let pk_rm = HpkePublicKey::new(hex_to_bytes(&test.pkRm)); - let sk_rm = HpkePrivateKey::new(hex_to_bytes(&test.skRm)); - let pk_em = hex_to_bytes_option(test.pkEm); - let pk_em = if pk_em.is_empty() { - None - } else { - Some(HpkePublicKey::new(pk_em)) - }; - let sk_em = hex_to_bytes_option(test.skEm); - let sk_em = if sk_em.is_empty() { - None - } else { - Some(HpkePrivateKey::new(sk_em)) - }; - let pk_sm = hex_to_bytes_option(test.pkSm); - let pk_sm = if pk_sm.is_empty() { - None - } else { - Some(HpkePublicKey::new(pk_sm)) - }; - let pk_sm = pk_sm.as_ref(); - let sk_sm = hex_to_bytes_option(test.skSm); - let sk_sm = if sk_sm.is_empty() { - None - } else { - Some(HpkePrivateKey::new(sk_sm)) - }; - let sk_sm = sk_sm.as_ref(); - let info = hex_to_bytes(&test.info); - let psk = hex_to_bytes_option(test.psk); - let psk = vec_to_option_slice(&psk); - let psk_id = hex_to_bytes_option(test.psk_id); - let psk_id = vec_to_option_slice(&psk_id); - let shared_secret = hex_to_bytes(&test.shared_secret); - let key = hex_to_bytes(&test.key); - let nonce = hex_to_bytes(&test.base_nonce); - let exporter_secret = hex_to_bytes(&test.exporter_secret); - - // Input key material. - let ikm_r = hex_to_bytes(&test.ikmR); - let ikm_e = hex_to_bytes(&test.ikmE); - let ikm_s = hex_to_bytes_option(test.ikmS); - - // Use internal `key_schedule` function for KAT. - let mut direct_ctx = hpke - .key_schedule( - &shared_secret, - &info, - psk.unwrap_or_default(), - psk_id.unwrap_or_default(), - ) - .unwrap(); - - // Check setup info - // Note that key and nonce are empty for exporter only key derivation. - assert_eq!(direct_ctx.key(), key); - assert_eq!(direct_ctx.nonce(), nonce); - assert_eq!(direct_ctx.exporter_secret(), exporter_secret); - assert_eq!(direct_ctx.sequence_number(), 0); - - // Test key pair derivation. - let (my_sk_r, my_pk_r) = hpke.derive_key_pair(&ikm_r).unwrap().into_keys(); - assert_eq!(sk_rm, my_sk_r); - assert_eq!(pk_rm, my_pk_r); - let (my_sk_e, my_pk_e) = hpke.derive_key_pair(&ikm_e).unwrap().into_keys(); - if let (Some(sk_em), Some(pk_em)) = (&sk_em, &pk_em) { - assert_eq!(sk_em, &my_sk_e); - assert_eq!(pk_em, &my_pk_e); - } - if let (Some(sk_sm), Some(pk_sm)) = (sk_sm, pk_sm) { - let (my_sk_s, my_pk_s) = hpke.derive_key_pair(&ikm_s).unwrap().into_keys(); - assert_eq!(sk_sm, &my_sk_s); - assert_eq!(pk_sm, &my_pk_s); - } + // Setup sender and receiver with KAT randomness. + // We first have to inject the randomness (ikmE). + + // Inject `ikmE` to check the sender-side `enc`. DH-based KEMs derive the + // ephemeral from `Hpke::random`; the PQ KEMs run derandomized + // from the injected seed. Either way `enc` must match the vector. + #[cfg(feature = "hpke-test-prng")] + { + log::trace!("Testing with known ikmE ..."); + let mut hpke_sender = Hpke::::new(mode, kem_id, kdf_id, aead_id); + // This only works when seeding the PRNG with ikmE. + hpke_sender.seed(&ikm_e).expect("Error injecting ikm_e"); + let (enc, _sender_context_kat) = hpke_sender + .setup_sender(&pk_rm, &info, psk, psk_id, sk_sm) + .unwrap(); + let receiver_context = hpke + .setup_receiver(&enc, &sk_rm, &info, psk, psk_id, pk_sm) + .unwrap(); + assert_eq!(enc, kat_enc); + assert_eq!(receiver_context.key(), receiver_context_kat.key()); + assert_eq!(receiver_context.nonce(), receiver_context_kat.nonce()); + assert_eq!( + receiver_context.exporter_secret(), + receiver_context_kat.exporter_secret() + ); + receiver_context_kat = receiver_context; + assert_eq!(receiver_context_kat.key(), key); + assert_eq!(receiver_context_kat.nonce(), nonce); + assert_eq!(receiver_context_kat.exporter_secret(), exporter_secret); + assert_eq!(receiver_context_kat.sequence_number(), 0); + } - // Setup KAT receiver. - let kat_enc = hex_to_bytes(&test.enc); - let mut receiver_context_kat = hpke - .setup_receiver(&kat_enc, &sk_rm, &info, psk, psk_id, pk_sm) - .unwrap(); - - // Setup sender and receiver with KAT randomness. - // We first have to inject the randomness (ikmE). - - #[cfg(feature = "hpke-test-prng")] - { - log::trace!("Testing with known ikmE ..."); - let mut hpke_sender = Hpke::::new(mode, kem_id, kdf_id, aead_id); - // This only works when seeding the PRNG with ikmE. - hpke_sender.seed(&ikm_e).expect("Error injecting ikm_e"); - let (enc, _sender_context_kat) = hpke_sender + // Setup sender and receiver for self tests. + let (enc, mut sender_context) = hpke .setup_sender(&pk_rm, &info, psk, psk_id, sk_sm) .unwrap(); - let receiver_context = hpke + let mut receiver_context = hpke .setup_receiver(&enc, &sk_rm, &info, psk, psk_id, pk_sm) .unwrap(); - assert_eq!(enc, kat_enc); - assert_eq!(receiver_context.key(), receiver_context_kat.key()); - assert_eq!(receiver_context.nonce(), receiver_context_kat.nonce()); - assert_eq!( - receiver_context.exporter_secret(), - receiver_context_kat.exporter_secret() + + // Encrypt + log::trace!( + "Testing encryptions for mode {:?} with ciphersuite {:?}_{:?}_{:?}", + mode, + kem_id, + kdf_id, + aead_id ); - receiver_context_kat = receiver_context; - assert_eq!(receiver_context_kat.key(), key); - assert_eq!(receiver_context_kat.nonce(), nonce); - assert_eq!(receiver_context_kat.exporter_secret(), exporter_secret); - assert_eq!(receiver_context_kat.sequence_number(), 0); - } + for encryption in test.encryptions.iter() { + let aad = hex_to_bytes(&encryption.aad); + let ptxt = hex_to_bytes(&encryption.pt); + let ctxt_kat = hex_to_bytes(&encryption.ct); + + // Test context API self-test + let ctxt_out = sender_context.seal(&aad, &ptxt).unwrap(); + let ptxt_out = receiver_context.open(&aad, &ctxt_out).unwrap(); + assert_eq!(ptxt_out, ptxt); + + // Test KAT receiver context open + let ptxt_out = receiver_context_kat.open(&aad, &ctxt_kat).unwrap(); + assert_eq!(ptxt_out, ptxt); + + // Test KAT seal on direct_ctx + let ct = direct_ctx.seal(&aad, &ptxt).unwrap(); + assert_eq!(ctxt_kat, ct); + } - // Setup sender and receiver for self tests. - let (enc, mut sender_context) = hpke - .setup_sender(&pk_rm, &info, psk, psk_id, sk_sm) - .unwrap(); - let mut receiver_context = hpke - .setup_receiver(&enc, &sk_rm, &info, psk, psk_id, pk_sm) - .unwrap(); - - // Encrypt - log::trace!( - "Testing encryptions for mode {:?} with ciphersuite {:?}_{:?}_{:?}", - mode, - kem_id, - kdf_id, - aead_id - ); - for encryption in test.encryptions.iter() { - // Cloning the Hpke object renews the test PRNG. - hpke = hpke.clone(); - let aad = hex_to_bytes(&encryption.aad); - let ptxt = hex_to_bytes(&encryption.pt); - let ctxt_kat = hex_to_bytes(&encryption.ct); - - // Test context API self-test - let ctxt_out = sender_context.seal(&aad, &ptxt).unwrap(); - let ptxt_out = receiver_context.open(&aad, &ctxt_out).unwrap(); - assert_eq!(ptxt_out, ptxt); - - // Test KAT receiver context open - let ptxt_out = receiver_context_kat.open(&aad, &ctxt_kat).unwrap(); - assert_eq!(ptxt_out, ptxt); - - // Test KAT seal on direct_ctx - let ct = direct_ctx.seal(&aad, &ptxt).unwrap(); - assert_eq!(ctxt_kat, ct); - } + // Test the single-shot API once per vector. This path runs a full KEM + // setup_sender/setup_receiver (an encapsulation + decapsulation), so it + // is by far the most expensive operation here; running it for every one + // of the (up to 257) encryptions added no coverage over the per-message + // KAT checks above, which already byte-compare every ciphertext. + if let Some(encryption) = test.encryptions.first() { + let aad = hex_to_bytes(&encryption.aad); + let ptxt = hex_to_bytes(&encryption.pt); + // Cloning the Hpke object renews the test PRNG. + let mut hpke = hpke.clone(); + let (enc, ct) = hpke + .seal(&pk_rm, &info, &aad, &ptxt, psk, psk_id, sk_sm) + .unwrap(); + let ptxt_out = hpke + .open(&enc, &sk_rm, &info, &aad, &ct, psk, psk_id, pk_sm) + .unwrap(); + assert_eq!(ptxt_out, ptxt); + } - // Test single-shot API self-test - if let Some(encryption) = test.encryptions.first() { - let aad = hex_to_bytes(&encryption.aad); - let ptxt = hex_to_bytes(&encryption.pt); - // Cloning the Hpke object renews the test PRNG. - let mut hpke = hpke.clone(); - let (enc, ct) = hpke - .seal(&pk_rm, &info, &aad, &ptxt, psk, psk_id, sk_sm) - .unwrap(); - let ptxt_out = hpke - .open(&enc, &sk_rm, &info, &aad, &ct, psk, psk_id, pk_sm) - .unwrap(); - assert_eq!(ptxt_out, ptxt); - } + // Test KAT on direct_ctx for exporters + log::trace!( + "Testing exporter for mode {:?} with ciphersuite {:?}_{:?}_{:?}", + mode, + kem_id, + kdf_id, + aead_id + ); + for export in test.exports.iter() { + let export_context = hex_to_bytes(&export.exporter_context); + let export_value = hex_to_bytes(&export.exported_value); + let length = export.L; - // Test KAT on direct_ctx for exporters - log::trace!( - "Testing exporter for mode {:?} with ciphersuite {:?}_{:?}_{:?}", - mode, - kem_id, - kdf_id, - aead_id - ); - for export in test.exports.iter() { - let export_context = hex_to_bytes(&export.exporter_context); - let export_value = hex_to_bytes(&export.exported_value); - let length = export.L; + let exported_secret = direct_ctx.export(&export_context, length).unwrap(); + assert_eq!(export_value, exported_secret); + } - let exported_secret = direct_ctx.export(&export_context, length).unwrap(); - assert_eq!(export_value, exported_secret); - } - }); - executed.into_inner() + Some(kem_id) + }) + .collect() } #[test] fn kats_rust_crypto() { - // The KEMs the RustCrypto backend is expected to run KATs for. All AEADs and - // KDFs present in the test vectors are supported by both backends, so the - // KEM is the only thing that determines whether a vector is exercised. - #[allow(unused_mut)] - let mut kems = vec![ + // `test_vectors2.json` is the newer vector format and includes p384, + // `test_vectors_k256.json` holds the secp256k1 suites, + // which is not standardized only the RustCrypto backend implements. + let files = &[ + "tests/test_vectors.json", + "tests/test_vectors2.json", + "tests/test_vectors_k256.json", + ]; + + // The exact set of KEMs the RustCrypto backend must exercise. These files + // carry no ML-KEM / X-Wing vectors, so `experimental` adds no KAT coverage + // here; K256 runs regardless of it. + let expected_kems = &[ KemAlgorithm::DhKem25519, KemAlgorithm::DhKemP256, - KemAlgorithm::DhKemK256, KemAlgorithm::DhKemP384, + KemAlgorithm::DhKemK256, ]; - #[cfg(feature = "experimental")] - kems.extend([ - KemAlgorithm::XWingDraft06, - KemAlgorithm::MlKem768, - KemAlgorithm::MlKem1024, - ]); - run::(&kems); + + run::(files, expected_kems); } #[test] fn kats_libcrux() { - // The KEMs the libcrux backend is expected to run KATs for. See the note in - // `kats_rust_crypto` on why only the KEM matters. #[allow(unused_mut)] - let mut kems = vec![ - KemAlgorithm::DhKem25519, - KemAlgorithm::DhKemP256, - KemAlgorithm::XWingDraft06, - ]; + let mut files = vec!["tests/test_vectors.json", "tests/test_vectors2.json"]; + + // `test_vectors_hpke_pq.json` is vendored from + // + // (draft-ietf-hpke-pq). Only the libcrux provider implements these suites, + // and only under the `draft-ietf-hpke-pq` feature. Unsupported suites within + // the file (TurboSHAKE, X448, …) are skipped automatically. libcrux does not + // implement secp256k1, so it is deliberately not handed `test_vectors_k256`. + #[cfg(feature = "draft-ietf-hpke-pq")] + files.push("tests/test_vectors_hpke_pq.json"); + + // The exact set of KEMs the libcrux backend must exercise, per feature. Note + // `draft-connolly-cfrg-hpke-mlkem` adds no entries: no ML-KEM vectors ship + // outside the `draft-ietf-hpke-pq` file, so that feature has no KAT coverage. + #[allow(unused_mut)] + let mut expected_kems = vec![KemAlgorithm::DhKem25519, KemAlgorithm::DhKemP256]; #[cfg(feature = "libcrux-rustcrypto-p-curves")] - kems.extend([KemAlgorithm::DhKemP384, KemAlgorithm::DhKemP521]); - #[cfg(feature = "draft-connolly-cfrg-hpke-mlkem")] - kems.extend([KemAlgorithm::MlKem768, KemAlgorithm::MlKem1024]); - run::(&kems); + expected_kems.extend([KemAlgorithm::DhKemP384, KemAlgorithm::DhKemP521]); + #[cfg(feature = "draft-ietf-hpke-pq")] + expected_kems.extend([ + KemAlgorithm::XWingDraft06, + KemAlgorithm::MlKem512, + KemAlgorithm::MlKem768, + KemAlgorithm::MlKem1024, + KemAlgorithm::MlKem768P256, + ]); + // The ML-KEM-1024 / P-384 hybrid needs the P-384 curve, gated behind p-curves. + #[cfg(all( + feature = "draft-ietf-hpke-pq", + feature = "libcrux-rustcrypto-p-curves" + ))] + expected_kems.push(KemAlgorithm::MlKem1024P384); + + run::(&files, &expected_kems); } -fn run(supported_kems: &[KemAlgorithm]) { +/// Run the KAT for every file and assert that the set of KEMs actually exercised +/// is exactly `expected_kems` — the concrete list of suites this backend is +/// declared to support in this build. +/// +/// Because the expectation is a hand-declared list rather than something derived +/// from `Crypto::supports_*`, a regression that silently drops (or unexpectedly +/// gains) support for a KEM trips the assertion instead of passing quietly. Every +/// file handed to a backend must also run at least one vector, so a backend is +/// never given a file whose suites it can't run. +fn run(files: &[&str], expected_kems: &[KemAlgorithm]) { let _ = pretty_env_logger::try_init(); - let files = vec![ - "tests/test_vectors.json", - "tests/test_vectors2.json", - "tests/test_vectors_k256.json", - ]; - let mut total_executed = 0; - for path in files { + + let mut ran_kems: Vec = Vec::new(); + for &path in files { let file = match File::open(path) { Ok(f) => f, Err(_) => panic!("Couldn't open file {}.", path), @@ -365,32 +395,21 @@ fn run(supported_kems: &[KemAlgorithm]) { Err(e) => panic!("Error reading file.\n{:?}", e), }; - // We know upfront exactly which vectors this backend should run: those - // whose KEM it supports. Anything else is skipped. - let expected = tests - .iter() - .filter(|t| { - KemAlgorithm::try_from(t.kem_id) - .map(|kem| supported_kems.contains(&kem)) - .unwrap_or(false) - }) - .count(); - - // Run the actual KAT. + // Run the actual KAT; `kat` returns the KEM of every vector it exercised. let now = Instant::now(); let executed = kat::(tests.clone()); let time = now.elapsed(); - assert_eq!( - executed, - expected, - "{}: expected {} KAT vectors to run for {}, but {} ran", - path, - expected, + // Every file must contribute something, so a backend is never handed a + // file it can't run any of (e.g. libcrux + secp256k1 vectors). + assert!( + !executed.is_empty(), + "No KAT vectors ran for {} in {}", Crypto::name(), - executed + path ); - total_executed += executed; + + ran_kems.extend(executed); log::info!( "Test vectors with {} took: {}s", @@ -399,15 +418,41 @@ fn run(supported_kems: &[KemAlgorithm]) { ); } - // Guard against all files silently loading nothing / every vector being - // skipped, which would make the test pass without checking anything. + // The set of KEMs that ran must be exactly the declared set. Compare as sets: + // a KEM appears once per vector above and across multiple files. + let ran_set = distinct(&ran_kems); + let missing: Vec<_> = expected_kems + .iter() + .filter(|k| !ran_set.contains(k)) + .collect(); + let unexpected: Vec<_> = ran_set + .iter() + .filter(|k| !expected_kems.contains(k)) + .collect(); assert!( - total_executed > 0, - "No KAT vectors ran at all for {}", - Crypto::name() + missing.is_empty() && unexpected.is_empty(), + "{}: KEM coverage mismatch — expected {:?}, ran {:?} \ + (missing {:?}, unexpected {:?})", + Crypto::name(), + expected_kems, + ran_set, + missing, + unexpected ); } +/// The distinct values of `kems`, preserving first-seen order. `KemAlgorithm` is +/// only `PartialEq`, so we can't lean on a `HashSet`/`BTreeSet` here. +fn distinct(kems: &[KemAlgorithm]) -> Vec { + let mut out: Vec = Vec::new(); + for &kem in kems { + if !out.contains(&kem) { + out.push(kem); + } + } + out +} + #[cfg(feature = "serialization")] #[cfg(feature = "hazmat")] #[test] diff --git a/crates/protocols/hpke/tests/test_hpke_pq.rs b/crates/protocols/hpke/tests/test_hpke_pq.rs new file mode 100644 index 000000000..c1cc047a2 --- /dev/null +++ b/crates/protocols/hpke/tests/test_hpke_pq.rs @@ -0,0 +1,152 @@ +//! Round-trip tests for the post-quantum HPKE ciphersuites used by the MLS PQ +//! ciphersuites draft (draft-ietf-mls-pq-ciphersuites), implemented in the +//! libcrux provider behind the `draft-ietf-hpke-pq` feature. +//! +//! These exercise seal/open, secret export, and `DeriveKeyPair` for every KEM +//! required by the MLS suites, paired with the SHAKE256 KDF and AES-GCM. They +//! confirm internal consistency (round-trips); byte-exact interop still needs +//! the draft Appendix test vectors. + +#![cfg(feature = "draft-ietf-hpke-pq")] + +extern crate hpke_rs as hpke; + +use hpke::prelude::*; +use hpke_rs_crypto::types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm}; +use hpke_rs_libcrux::HpkeLibcrux; + +/// Every (KEM, AEAD) combination the MLS PQ ciphersuites need; all use the +/// SHAKE256 KDF. +const SUITES: &[(KemAlgorithm, AeadAlgorithm)] = &[ + (KemAlgorithm::MlKem512, AeadAlgorithm::Aes128Gcm), + (KemAlgorithm::MlKem768, AeadAlgorithm::Aes256Gcm), + (KemAlgorithm::MlKem1024, AeadAlgorithm::Aes256Gcm), + (KemAlgorithm::XWingDraft06, AeadAlgorithm::Aes128Gcm), + (KemAlgorithm::XWingDraft06, AeadAlgorithm::Aes256Gcm), + (KemAlgorithm::MlKem768P256, AeadAlgorithm::Aes128Gcm), + (KemAlgorithm::MlKem768P256, AeadAlgorithm::Aes256Gcm), + #[cfg(feature = "libcrux-rustcrypto-p-curves")] + (KemAlgorithm::MlKem1024P384, AeadAlgorithm::Aes256Gcm), +]; + +fn hpke(kem: KemAlgorithm, aead: AeadAlgorithm) -> Hpke { + Hpke::::new(HpkeMode::Base, kem, KdfAlgorithm::Shake256, aead) +} + +#[test] +fn seal_open_round_trip() { + let info = b"test info"; + let aad = b"test aad"; + let msg = b"post-quantum HPKE round trip"; + + for &(kem, aead) in SUITES { + let mut sender = hpke(kem, aead); + let kp = sender.generate_key_pair().expect("key gen"); + let pk_r = kp.public_key().clone(); + + let (enc, ct) = sender + .seal(&pk_r, info, aad, msg, None, None, None) + .unwrap_or_else(|e| panic!("seal failed for {kem:?}/{aead:?}: {e:?}")); + + let receiver = hpke(kem, aead); + let pt = receiver + .open(&enc, kp.private_key(), info, aad, &ct, None, None, None) + .unwrap_or_else(|e| panic!("open failed for {kem:?}/{aead:?}: {e:?}")); + + assert_eq!(pt, msg, "round-trip mismatch for {kem:?}/{aead:?}"); + } +} + +#[test] +fn derive_key_pair_is_deterministic_and_usable() { + let ikm = [42u8; 64]; + let info = b""; + let aad = b""; + let msg = b"derived key pair"; + + for &(kem, aead) in SUITES { + let sender = hpke(kem, aead); + + // DeriveKeyPair must be deterministic in the input key material. + let kp1 = sender.derive_key_pair(&ikm).expect("derive 1"); + let kp2 = sender.derive_key_pair(&ikm).expect("derive 2"); + assert_eq!( + kp1.public_key().as_slice(), + kp2.public_key().as_slice(), + "DeriveKeyPair not deterministic for {kem:?}" + ); + + // ... and the derived key pair must work end-to-end. + let mut sender = hpke(kem, aead); + let (enc, ct) = sender + .seal(kp1.public_key(), info, aad, msg, None, None, None) + .expect("seal with derived key"); + let receiver = hpke(kem, aead); + let pt = receiver + .open(&enc, kp1.private_key(), info, aad, &ct, None, None, None) + .expect("open with derived key"); + assert_eq!(pt, msg, "derived-key round-trip mismatch for {kem:?}"); + } +} + +#[test] +fn export_secret_matches() { + let info = b"export info"; + let exporter_context = b"context"; + + for &(kem, aead) in SUITES { + let mut sender = hpke(kem, aead); + let kp = sender.generate_key_pair().expect("key gen"); + + let (enc, sender_secret) = sender + .send_export( + kp.public_key(), + info, + None, + None, + None, + exporter_context, + 32, + ) + .expect("send_export"); + + let receiver = hpke(kem, aead); + let receiver_secret = receiver + .receiver_export( + &enc, + kp.private_key(), + info, + None, + None, + None, + exporter_context, + 32, + ) + .expect("receiver_export"); + + assert_eq!( + sender_secret, receiver_secret, + "exporter mismatch for {kem:?}" + ); + } +} + +/// The one-stage (SHAKE) key schedule length-prefixes `psk`/`psk_id`/`info` +/// with a 2-byte prefix, so any of those exceeding `u16::MAX` bytes must be +/// rejected rather than silently truncated. +#[test] +fn over_long_info_is_rejected() { + let (kem, aead) = (KemAlgorithm::MlKem768, AeadAlgorithm::Aes256Gcm); + let mut sender = hpke(kem, aead); + let kp = sender + .generate_key_pair() + .expect("key gen failed unexpectedly"); + + // One byte past what a 2-byte length prefix can encode. + let info = vec![0u8; u16::MAX as usize + 1]; + + let err = sender + .seal(kp.public_key(), &info, b"", b"msg", None, None, None) + .expect_err("over-long info must be rejected"); + assert_eq!(err, HpkeError::InvalidInput); +} diff --git a/crates/protocols/hpke/tests/test_vectors_hpke_pq.json b/crates/protocols/hpke/tests/test_vectors_hpke_pq.json new file mode 100644 index 000000000..f63ea7c7e --- /dev/null +++ b/crates/protocols/hpke/tests/test_vectors_hpke_pq.json @@ -0,0 +1,1380 @@ +[ + { + "mode": 0, + "kem_id": 64, + "kdf_id": 1, + "aead_id": 1, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "a4f31f1285babd975087bad56c61ce3947c2f537d4256e184f5657f8ba4c7668", + "ikmR": "53c72362cd4c0d3c04fb963bb2d8fa3b61be2a83befb53883892f68d1e6af3ee2ab07a445a87cd505fe27f3434e35c8ad26e6452b51f24e5c9d3d174b326fb0e", + "skRm": "0466a81fc187205d5925aaa518e98d6cbde2a1aa63d756da4a62f873f6a0b1f1418d0eec2620055b8537aca724d18ad436e47972f85f4c5c5d2cfb1c62b100bd", + "pkRm": "3e774db858732c35a408388fceb66cc61777d361c85a72b1e844422cca0effcb5778cc5de43acab0ec682b0b318fa4122bac224d10c193b5933758320587196fd50cf76c94a1222b2a330a9fdb32b0ec8a42931c531bb025095e49fc0df8a5205b32149e7354d63232c8199dd9e6654ec0bef0937484b0904950b05b29297dbb410be008ad441ebce23052c8cda593bb1bf4b5e0e520ac4a53e9a1bc38591c3f723e66c177a6715d3a365b0c156a5f72aa439ccb42944b8f47a32b446ab6d8ce58096a778a2322b3b467f2c5a17875fcd6a69ee74ea297093798765f6851e6402b77c723b335c5c8857d94090d41fa2b5e54ce5b7194d29175f141718c36959e6142402b2e816a856d914b1f2b3fc62329cccc7e23fb9d14828e44941997b323bcc90c497579d49462d79671809a38d79c3137cc4258563134d4662872260cc13b5c990215959721082c827bab0cb9a2559b16eb704cdea7cfe60b24224b13d055b382ea9e0920adb3592689b3635239ccf8db631f585957a37c57fa8d92fc7907d0266dca9b55fa5b68d308d8d6cbdcd8b583fa804c03ac620003911b506396709bea2ad1a7a83f697c9ec741cf650464bac0093a0efd462207620c13b86cc0dc10dc9442870250390384c5e2b8f5294f9c88b26b09c8d504c286d16109a56838830caa35b231811bda3a1677d3087ce216ed9c8ce5cc4020d290efcc60ded511bb990795d00674a12641e885ceda249e387ce470716188cd359c66f4e61908e12757b97c3095168fb8714681a96c54c1cd401231a5500896a8eabaa521da680e759222d9765746624ab6c79a754b10477a0ae12ba2175f6f701569bb15d2ebc4e6e9b8e6f1c021b31edccb152ea23365db5ef396c893a9ba12cca8a3847e99f6c732523e55844a17ba34cbd6042d1b7ffdc47d6031a7587162ada1a283267eaaa31da17cc611038fff51446e0384c7397450b05084859cb8f79e6a1775710a19f8e9896e83861debc6ffe1ba8ebb1cdee95da61c30f6c99091e31b4f3c593352a1253910261c187c60a420e1445951cd797b74a4a7b53b50b0c1370e69e65fcd29aa553682cc42f6802ac4b8a3bd7b1c482ff85523aa2848b95ee9654b55af", + "enc": "a66b74747cbe84af3c6c824792211ab3b5ce0847f49090036c4ebf5b9767a6564c0c6cd52137245582e773b5dbf530ade89b05e7df571c278476b5f874e5aa1590a47d9ee2c4d2c447eb4a070e86ff448bec7d38412cb7df4463b2d42ce0691d59a97c25f0a2b6b39f07ab04e4c9b11e6a27a738e9c3b6869ca803602b5fb78f071e3f447845fde4d1d0893f650ea246eb599bcfbef61e3d5f03c6a20bcb99c610a22712045c8e37f549c353949ce27bfdd953bfef97469e1a46696dde84326dd6a7eb79af9107ede1b61f4d5d17c8859a604dc0b67fc712f545efbc8ab6bba66931396769874794ad44654d63e57fb36a8ed188c9dff164100eb26581d0853719f88619220ba1815f8d737727a35bc33227e2580c5b68baec549e0cb722caf24a4ee28cf585cc12e7de2a845a5b0599cccd94a49be72acd52e0eb1c26cb764bac0e25e93fc015456ea2f6f2807a47a46fe5c1715394a5a913812ee17a4684b9857f229b61add7440301e12d00b4cb2c406a28de76bfc31b5c239dc96d94a3f29f3a85b507118d0c66fca652c33da63dc246024f429399c1cd9531e6e85c6ef30d6954270c895c5e318744b5ed728ea326f242e1be5c519bccdb01668704d1328afd97476157e1322525b994a7a48d3ed1b9097a9eb632b84a92e257ef191fb5e469cfc9a5943175b7c52e3e0a83d6df64038fde3cc2938bed141a5e19011247f87183602f5b98dc495fbdf463e8ede9da6f970632dad9cea242e91d1681ba801be84190e1a13e6d1820fe846945a41d920b1f717f12b10d70a9b203377316bfd3f6217b758a949a899de90119e934fdf0daae68f6a8b0b89f93064c727e14691c4149c1147d1ae457d127eda31a4b63fd8aa7f5e501b3e1de20ee1024c7cb0010a0996adebb9527c4919ac3903296ca8253facf1a225faa95aa7b26889a1302132cbfb519cda8cb60dd14646c3e94cc335881015b0e63191b82711fd498442cb448cec3c2581e26019632f66c2d301261e199026eebb351866d82212c1a5b4acef12e7c22f4597c185301cf606f6ce69482f81a630539d3cd1611875fa28a1d", + "shared_secret": "996dfcdb0c50e9fa4748dfaf6a641ff4e26de2f84e1d19047f5bb0589043e194", + "suite_id": "48504b45004000010001", + "key": "7bdfa98081ef3777a154d3cd10018539", + "base_nonce": "b9bc4aba6b886a03673e7083", + "exporter_secret": "2e2fe69cfa6dc979c005cd7adeee7d44a76f2aee89210b36e7a967fab89069a7", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "c80cc04803277f688c29c5c0a9f222f1977c7bfc5cc5e66ff4210c5bc315ceb347135531581a411dc61bb35059a781fbf8c52e9539c1e55bef647086ea64a7cf3e5d6c4211f38f747276", + "nonce": "b9bc4aba6b886a03673e7083", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "f3c5de027b9b14bfe43fcfdd66c136b47ccfeb03096a212f480c74bdbd2987c9844b103d16ce2d98dfde273ac757a389bbe1ddf7295c1b6903495fe54caeb337f7a1856f3861a888a051", + "nonce": "b9bc4aba6b886a03673e7082", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "cd629625b73b85743e4d88636a9459d0222e45704d7a5d0bb22b8aded487731c7173b090fe56293d06e822c087ea227271e08e1f6ef7a3b17e630f4c74545806723662fb03fa8b6d059e", + "nonce": "b9bc4aba6b886a03673e7081", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "a67fdc43fdd76a2855141ae9658e00f0b9a8fed0c4d39d909e8545c2fe0116a046f0497ffc3e82cbf98e95f7a904cbe7e762bafefa2bd4778a4b314e08face73e4787a6d7c931d648a5f", + "nonce": "b9bc4aba6b886a03673e7080", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "a188bb14db42930838fbd89529f61afe0bd6c0f69c80df4ffac6a23496063dae97cadf414f07cfa6911756fdb217a5ecad4cd6939a22ea43b3bbeb22c6a693b7388f5521cbfcbacb9b4d", + "nonce": "b9bc4aba6b886a03673e7087", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "420090b7e3df69318525d3b2e92457314b6f46ae8b9df23e7c0281220d9bee9f6e4da96aa152f323df7ade4394ae8adbe3fd0596bcf47150df455256b9078574945953c3f281d99d09ab", + "nonce": "b9bc4aba6b886a03673e7086", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "5abfcc4c7b18a59b59035f21763ef08ceee47c5f45791f8c9925c57befa130bfd45cfe4b285f0523687e00ae1362aa771a2f35140ea92570fec2e0d4729764c5eee3c65a391491fc6324", + "nonce": "b9bc4aba6b886a03673e7085", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "0570c5db7c995d69180d5b4fc031d065dac7833a3e1891e23c2d84a8e1286ab83c5066ddbca722552d9f67687e9a627cfac6e4429295c4470f832906571726d0a1ab78ac04a52e8b74e7", + "nonce": "b9bc4aba6b886a03673e7084", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "31e1aebec642edeac620b97333caca70d5a747d05fd41f17134e919c827515c8977cfa7238032a33bd4da61a4e10688d2e338ea92741de85a76ec393b06228c558b2e49692b15be681cd", + "nonce": "b9bc4aba6b886a03673e708b", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "59047cd54181a97057b845b22b88dbbab6dd37c862f837cf428b932c8027439f15120082c5630a6021361bcc00d6dc4bdf6d4840d0772ca8be99e5c40b448029ef3cf320b6b63d3eb6da", + "nonce": "b9bc4aba6b886a03673e708a", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "c0186fd042852629d81ba939012f98d444a5c19bd7cee946389fa016cbb3d9a3" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "d4db343a5d04f812edac36da2b3bf29cbbb10e058b94de2a9a3ccabc621783bf" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "f9ba1dbc672d27b24880c74d16417c0e6e0e0ff68fd37684aa654b3e915289a3" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "cb7c0165ab19dad598712b092c3b03b9569aa042582a7c3750aa002015103386" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "c6c8ffed2d6900062c3ec5c92ea82fd68d2063325f320e269d3f1ac8a0530ad4" + } + ] + }, + { + "mode": 0, + "kem_id": 65, + "kdf_id": 1, + "aead_id": 1, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "9b933cd9c9421cd58db0c5f6cea53eedbd7fae056ff95d688d8ed9a58177e76b", + "ikmR": "a60b35f174ce9ac7a4ff5b9f81e38125b03506ecbd56a3a55c31ece0f59070520729773a61a499d5137daaef824b493848b6e4dd332a815ff19aa9f58a381eb8", + "skRm": "80008d036609972cf761d7e2d3b831e48d3e941cda94fbf9bae09bca87373f9bb7411f58fd3324ba1d0daa5a7b42768c5b53e1df29c28d4f5428a8233a905089", + "pkRm": "1a9664765a7f3322c86c451287f56dcafb799cc39a17e8c33f911a8703b90b3a99bbd712962c0eb0b9cca65843cd784ada958b261116dea17e0fa2533ca23498c0793078c5b8254d2a162e4042085d3c164d4615270bb56e4393672056c9f1babad3b95307b04ed54caa699bcc3cb24b1b488fcc5448d65bf9d8cdb9cc2140b7a18535232c14432e4bcba045bf80e00c8110679375406f278e96218de5662436d96b161829d23bceb66c338b78ab2eb956f90b867738754763ce13f35eac655aeae10949a582810a625964a58f1a6d15ea52ceabb463b211e0e1b19f736e1af4a7d2c02485a4538b1551fcccb996a605ec93719c876a5c86a8782b78565c603c538856228257033831eb99072a1a2d0413b007a7fa9a013efca0ca9800bae097b12790506640cbbbb903d545b87a20adabaebc7c46c781a11fc08f3dea902a3064ceab86525a3e33758e1fc76d17eb9796e0afce3099e2b64300d4586b24ccf29185c1e26b0ae29925527e79607e48786d27d651a290174f966200b3cea6b28add1368a410c753b692f5388763e9530e3a4db61627a56b70d6fc545e5b8712d3546a7c0548a03efe3a1312091434133520b21dc04b6d8b70c01894ca5714cd811b6f8b671544575bb5016d5e8788abb0206b22a7d8f64a09ca4f4d773ec7029a6bec726aa612bec0213377b3cc8a9218b465abc8b242937fabe57ae932151092c3cba5ae2734088f67244346c93a024506485af5f7ae6a8257f04b912e6c5ab9f12777473b8efb73a8056ee006294ae93ea03a204a09a858d87b09d425881819716b4cee09461daa8fdba8ad0ee81aeb60bd9ab59ba9c59381d3b147244c1d2285b5d79d5154abb794170a945c1a0acf2e4c82a5093371836bd153b6c9bb0fd3f21cc682bf1ad8467ebc88002840815a1bbd340603a8a45c002c3613503c6ab4e8620150c2331987559d6987900a803b05bd4b609f3ef6cbfb74c854eb83552c3db404af93746918eb7afd920434681a2eb618259c791ae58fa7e43fc083c14e3001e09c82e3e31efe61a2126936a7560a4239c85d366d01c67d08a2158d3605e1a8335e42b4c1d8457be130451968ab3c4325553923442892c9bd2a238b942902b6d1cf5603674811020330c9b8f77d274c885a982167ea995aab3c4ad4c15a2368ab8bbcb3f27064e3cbea93b652c869db424f7722839f4b55e1acafd0a4b1d6b561c3a416cfa2c175933da440b01daa4970e3b1888814dc972fd27030b15051d8348ac75b91752304acf3961670341b334fe9591fd60469d1dac085ca745ae0cbd7d48f951746ac523c42679a105978af3a36dfe36bcccb9386b96c3c498483d7c8ad869204d7a298d085105c8cd40601782a874bb8337bd00a2cf39650dc499bf32e67233219bc597165124e194252d47f23d78df18b43dcaaa29640269ff9197db15ebf919b3c702e16c01dd80881f627600d35c60c059daa53cd6114cf29f739302996cb3132e17a2e852038569223e2fcc23f6a90559b9b4a1059d85b827fe2568558376cb946f9bca484030988e53f61431ab60a276d9b43a85b38637aa017535533a00f72064602948c83cb1e050b0584a7628b2697d23476af2b0e68b5d57d4718ede11de2080798a309cc8102fff10ad012bd3d32efdc2052d8fd", + "enc": "86a740f005d8a10afb812bf6d0a97ea0c2a5c7a729af0c286418726ace66995445a5f6fc099b498ac3ef9e752ddb7ef88bec618c7fb4516385d681328381924c0723d92ffc9765a25ee558e29b1f7e8a38aff5debec491ef6fbbdca10170f54c2bd08aec077920e59380e5cd81983cecc15b2c4b201f2c2cf70640eeee3224a7849d8efc6404b317ef3b81be28dc1334ab4c71b16682db94ae7115da8069fef82a9dba4ea1671cfb5707333e4e107631016934480368514d3ead43fb3c916dba86da2071066d288b12b8c9397757e643c41ed7e240c5bdd924e30d923e90af5d03b7adfe1a3bb055195d37309e28a55a10ee859f812d06145a6346354ca8dfd72a829ef348de166d5dde7e41f60d3387933b41ce33d29c134ab96f2982c513884e7bcb790d31f0a8371e990c6cc9c1572d25d0efc7c0e979c88e1b6935d74b7ba78d53837ca5e486e8da5e6055d13e0a1f566cbde09caa2e73c1c1f0afb2f7db73a820a738a997639a96dac040e72eb8b18f48e7d9e964e3625ac12883a8a10d2f4f907e7703021347885081ef38c8445698717b947aa7df75f1dd3c320b71a431dd5d18b0de1ed307dba95a201aaf8ab37d8edd71c5da6097cfea602429abafd9924de42757fcd203b8bc6feafdac6d4178c7dfeda5435eba612a0a0c603171ce6ec491363c706730667445f69c9754ec0922c9f5dd593894f5c5a9284888376d22002242831babcb86feb2c7ec5df5b463e2469fd7a80d4aa6ce8ac21f48dbb95de64ddf15b1bd0f65548122f8f61c9f41a3ea75a2d66e970a04a79dc73c73b3e1ae0420677dcece4b36338d01ef7ff381a09c9aaffd1fc4f46c2461c0b8ee92de43379f086151d7065c4acac7a3ffe9205db754f717f9108ae4e364f6f375acf5a565de7e2c1249a353df05258c8eafdda03410ba2778714cacb64f61d494c4d5bcb420f718954a1db1e937df6b2483ca88b3d78476d3b2c78fe068ac2b584832c73fcccd2ac38aad93e35c717c1e172ac096eb2909f63ba780d009fc7c83b3acfe5be95be3ab11c25c86638a2cf3a2ebd23cb15b501c77eca0a28dc0a621ef1f337fcd7138e01d1324e985bd7eaf6d745530334b052b8d5c5b22064a623b9c7960d128e1665c8c7f8869d5afe7ed40f02f02edff64c577fd1084253aa40b1f8482725f6416f3e4d59add5002ba6b6d5b7be87a1aa3af89bba6c40838011f93dbb1bda2ecbd24a446619d15841f1ece8e0fb7769bd350d6bdd23ad074d0ef2d683bd7a6e64e97039aa92ea70f84f42ccc6ae0871da60362de0dfc6ea338dcae3eb2bf4c0e0746588faac369eb8b6b0e38a7a96d265fc5d4a91c994158168757e9750c80a90b571ec6c914eebf67901c7974732947d3871e41c9d59cd78bfd8e7e1fa6023545d54d2070ef64ecb70daa01f0b508764f6a3ad19d680927e78f86a664e62313b57941524bd9b691cfc514ecca83e172bc1e0c2b2b62ea7d896b3f4218fe39c1fd892b9852ac170524524205ac19ac58102a4e7562d2dd453d80", + "shared_secret": "750477fb7421ec8e787e4505a99278b0c8aa15b9783453e90939cf1527617dac", + "suite_id": "48504b45004100010001", + "key": "7d1031a2d6d232331f70495250fabb0d", + "base_nonce": "5974495634213151b309dfd3", + "exporter_secret": "abafcbaa704ff2bcd964ec7a3ad23cc66ff02f0df43576d744a4c2cf1f51e581", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "4b7dd443eecc37d978fb2e41808d8b3025e0afdefb57b96be0b2ee1c1e437a6a676e379812eac544f55e463d07b20cbe88225ba97736c48ba39bdd96bcd783b43a67eedb77bcd612820b", + "nonce": "5974495634213151b309dfd3", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "86e2d78ff8f07fc10e651796c0b51516200dfa224b35a99b460c9147eda0a42266cffa5763709ad8ff6ac0db08ac9a33ce4e8eab643380ea55fcd1d272dc8ecbed98d3b8e60a53805187", + "nonce": "5974495634213151b309dfd2", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "0908bf6f5163b23a220d8217ad53ec21dc8dbeb10d0ef86fb116c0c4a29a56a88b49b596ac7b90483b2bc64bdeb23006973992aaab358e825259acd3b56b80eafa635abc230911af016c", + "nonce": "5974495634213151b309dfd1", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "5b954fb94c939049b353a048602554ab2f57492e0f1de9212d805109cc967ed2aeadb201a300a6e7974af067796292b9020dff0379428b8583de25bf5c2c837f4371702e64974ef15f07", + "nonce": "5974495634213151b309dfd0", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "dd49fd3e866b6dd14af37d0cbf7b513a2bbf7657e676a858f4805561f28e046db1a808e43b9cbee3041c4a324289725a85621fc3de8344bc98c0f4706b4dbd8beff8414ca86540a51033", + "nonce": "5974495634213151b309dfd7", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "831a48817c5d45e330753a17cee36cc20326627e4c76e7e549a8448565d045a5350e609d42e0d1b717182f9907aea04df58ddc4567cb5a10610ce0c3604fe67ced8c52b1763261ce9a3f", + "nonce": "5974495634213151b309dfd6", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "5336076be19a0484403aac51a5e4459c03e0568ea9134d39540b1836f29a614e1c36cdcd55cb88e3e880d034ec7a0667bc07b67e1af39d9742993013225d4f07a1cf6b65352e4fdb010a", + "nonce": "5974495634213151b309dfd5", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "155ecc67e13d547588a220c2bc91ccac84408a109fdc14f9e94bfacac69fad13990f69944d14b46cf64ea95a9012f25e04a3dd1e6bb2ba7504cdfbe4bfdab29c81d2bc7677f118a3d2ca", + "nonce": "5974495634213151b309dfd4", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "1b48a0017b4a6c70b3f19795e4b4ec4e668446b73127e737ea06e0a8d25f2d212623bc3fe4c6b8fc508279d7e01edf441581f4647b4be97958658cfe06dd1c3bfce17345204184ec1a04", + "nonce": "5974495634213151b309dfdb", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "a8620253f72db37e2aa316a375f08c62b466546c84669a024194e7d36fd9864d5325420123e2ff4dafd016638a3c138a729bb57f0f1a4dc6c072801bb3f66b3f4794688206b69c9fb284", + "nonce": "5974495634213151b309dfda", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "4ce822c6932f0cbc2f1fbc3a652bbef4976ac63833d35fcce20b35c4a3d05443" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "f8a7549322f1921e48ffc17b05b71d54640bb0253c6e4589b0ee748120d9e735" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "7be483d9a999b4ae759bc3ea1a713bc989540fdc376c36472a7c1038a6c6ee04" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "9acdf000eb38f40b663ac4f05e2e86409499bf40dd5b439303b4e575b2091e53" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "4a48d80f407a18636fdac0d912020bc134e33d1e30e0be7fd36fb4b56d9849da" + } + ] + }, + { + "mode": 0, + "kem_id": 66, + "kdf_id": 2, + "aead_id": 2, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "54e68c4d0f72b94d956acf637c23570e505db5c08c0068bd136cacbc7dedda89", + "ikmR": "d6688a981deeff1d1273426af8a44aab877c50b6e8ac74b11e01a5960d97c03bffd9634894d255c424c80c74e0930b85b9f4c60e22a3efb09f4bad4749be427b", + "skRm": "73a9ffe155d39edcc023b11171ad6cf541ff85eff68c33b521ba25cbb1b7079bf848b63f5b8ca53f809255b51f1bef24b342d706a77cb460981e16b2ce737552", + "pkRm": "aa0a1b451a9aa747370a94ba416aa977c5bac5b19def1a59f1e9182564b1b8d4c761d30c598415ca200f30c2638d8635e4e8b67df03e207c2dcb2048e01590f5da7fff8c5d6657c32f5c2cc5e6b807e647a50b252817c9063bc542282cc1fb6f400b2382e7b39407c5234b6ea2319828348dd1a8b92f506f67eb686726138781337573b6ae878133b7c8548c307656c817f529d8f068f031c70b044ce1a09d3f8c8caec215dc3a68ad805617d11aff61c9a2d1632b4b7fdb8b67f37a2814e9974490b307b3ad3ccc9fbd3c577d9c147cbb69ea55c604f67895e2921f8b75d3b71e7126032acac6d2e5cbdad32587b6a99f9b12df949822e6392394a9761cc1bed0155bf28338e50c8cfaa1f0115f4c5384f6e8c46885588fc79cff4430fe745d4b74c1bf85cde60236e3b0b87dabac96677ca6c0cb7372bcf39708d5c1aff38235829b67d6d51eaaf36bcfc4c82980ca024a0495d8c3fd5a8ada25a3f7b33e78fb86701b710fd0b39f4b9b5649a654d719e5a96d63ca306e3862ccb52937a3bc1cf8079a996a27ba34bd9c54cc9b99fd504f758abb3ca519c6521095b8964c478063a2a5e668c88eac1feda35ae0865b1cd83bc7d1513bb21188b7c81d71cac1048756680fe15493c79c5b3de8563698cc900a30833793a46a7237623cb70b151024855c69ac2124ce426c8375a0874c6718b554875608935124a90357c277a6c4bc487266066bb0d7a7e76b5d3142a6c2aa8ce6da7540794eceb9329da1244e8b9fbfa0303ea9511d9c9a5ce39bd3c6358e4914d5a96a1c6bb47e7916b2c7114078c003d939e4a22118c7626d995b5455c6cb07ad59275e024944d599c817648ad0852625525a34fc1343325ebd286cfd2bcfd856cea2078415c49e485abb3bf8bbde7a93bcdb0a8ab84f4712370f71bc1f80c89fb9c548613ae9b267ade1c50915c3b6d9a464a6387ce71e2f268bae278e264acdc8f8ced1d85047aa2ee05a0c530117e1e06ff75624eae953938a7bac13698bfc0139eb930ff799bad7ababd85b74babcd6d05212b55db227339694cb72258bc365860d56bae2e21c5c886286e9a10f747d9345b9ba25cc741a742dbc1dd1193eea58ab08496f1a8bac7520c5ad109ff3b8380f764c0ed84008e54cc56c96a32187f6ba972fab323100b851e0586427223b67b4ec32011b33053bd1ab7d515b3ff02cef9c8994411085abc07d4b43c16147f15877703598d5626a85e56e665724d4f62c07590c5354109d30b46f6b5764fcb45e2630d18b6bc1d767e814ccb215cce3861600d1b17be16cb1e05b26577ef53c7f1a0c4ea15488fdfc52c5808c9c2854fcf1633f977cd9333fb97933a9624bfee00872dcaace051c189551a45963d9f20eed23bbcedb37e9b29039a294a299795cf416e8990d76f73af6474d157107afe0ac5cbb16a1241fc013a7350011b4ba70d9ac50c644b479f23e81ac986bacbd462b980f7a17c8426dd16a8a1f6289221255a8437a1280bd06b4c8a8f1b566c13a5841abc6aab414264932ba7d56f94e8970570d8586ff6c7754822c0a0c21cd2b190a8a3ce6294346a430757b40361833628b603a9c331165c8c7c55a45ab8b57118c885c93eed7b7fc8736ddd0a10f8a0d1d4b1d3f4bb41b09c67269493b03ae004917a625c4ccd064a21a822d45afb4d30b64a888460c37b1fc23082213cf468b10c60280ba9c91d68cab57433ab6ccb7474f0c0074ce23ac09e28e7795c72f496581878a999097fe940582b4af1784267bab2af681119dd58ab35ca006c04e7345091cd489a0e4c42ad1cdd300bc13b395ee3121a9276ca01b6c2382212872729f9b9d10f2a7778085a22966f44601c72524b8b168e368cb83466f06746c0df330bcfc7019456a9740802cf6923b508a3326cb53265a4a3b961077884e38c073d29de9c81189410467ac6c46f7c38e5142528a945ad9a510115122f519ca937e84e075260a323d900bb5e321080c264a95543106481cecc0d48275ebdbb4bdb6b5a9794a42d94bfc5c644fc8a5f3b7bbef1b3ba09a48473b3d2933b61cb474d292764531821fb72a0eb72de454577a126ecd4778722c3c55e3b09d860a0941189e680847112b759963a1862612ab7236883d177067ae105d6db70d5aa86aa243004121baae838830b87c6ec203168987cbe6a9bf8765bc7ec4a51ca64cd7b22cd5a3877dfecc3c", + "enc": "a9a4421ea715fc52329204452922e20220e14c1488bdc5f5b9e33916ff8c4b46481b8cad2a8b383f06b629908c71f7f7816afdb03c0a3e97fb58bcddad60cadd46582c182e4c75c69283f5797efae3b0ba5d0957a8ee460828c53b925d836a1616e564e0c2df7342400fe16a2efd0d441764328be60229f172d3244102345367ace697c8332e931b32adbd47ed63854336a5eeb3128a4b555568dfe926206f93c52285fc036e26a5d55e1e40939f8504877e0eaf2744549e4c6fcc4bf8e85458dc66294699fb146d0be363b03444ee85cdf57cc373d463097b8015121f91c00b66fd805d32ff0fb09a5c8c5af225f3c7c4d7fb7c4a39cf75878b16d3107edcd80ad10a450b1035b4144f3d662486b9e05f46f001ff8bf98688ceb4987bb0cdd75f58e184419c80ef55bc4ec0295fb290119af95d95ba24c0e2d0c371af7ad7a6e4a34b635dbcb2961571eb64e8087b8a2c16b2a2a4f71e94129bbd11bb4a2678dcafbd23bb6add7a3473880773f9b92812637b672edd418fb2630fe94d481789657d90afa4ceb7617ce2732eaba2c6a019b03ab7976e886ef9b50affc46676e536575b46dd39fa95e1f6d242914fde952e07c789a6c41b0c53fa3423173bbaeba6b578c1ef84d5a49a044c69aa6cdba1a7c1b373f31ca39216c8713469b1f37ac6436f4ec3e202176f767416b45421eee5c9603b26be09ddcceadc052bbc71f5f32867627523772be84e62878bf6882b5b3c863e0a79c89a2efb0744ce880ebe3f5c729baec24ca2c6541cc79f6e32a8163386a99c527233cfe88521467c6c2dd786f4957834b4b24729235853622579ddb427929676b8e338de6e08b512c3b26bab191a3d7ea2f97f6b5c56d5d92df4e922fbfbe16c748b30ff1d2816d7d8431b79ae3432a9f8d09e9e2577c1c3cf987cfaa17d699716892d4cbd8d5cb4fed656d58e1b3f5acdc6cac8afd31dc50bdfc8260c379b6479df9770fe752a9c1a34c5da2671fec505d2da1dcfd3f2231d20a812908e73709d144717ba761ce5e200b65af01ac5fb13e86ccc72cdebbac15e0f45ffba10120b844b5c4619ef546d5b493bdbe4c90947bbd3023126c099cc6c5a916a46cef0ba465c4d4734f2d0a4504ecc33f674d3e2560d2df0f201acad2988e454be3247aade5b5d7a2ca3059e75f1b09cb3653aed9139281aa66b21ad3ff8d8c4f331b253078c70173d907e41140307b495cdd83de81b12ada65c441a50d834c32ed661a1686f2bbc57736b2b859302b545fb2c4214b5cc9b5b85e56c65ae02a1b15e561670019d477639773026e3d233578f6a61e721cbb60c17a0d4035704b6dceb65c3e4e28772fd94df756b6a50931224e6d70f51993031fa96770b1d9df23b6fe1e0b6707e08a95f40357140287586b18f2cebc36544b90a82f086474fd1728f7d102e9f448f27fc632ec1805e0afe41061ac0501e91b5711e0431a856490ef6b2ce206d51d40dfcb2c6320aabf4904b9a58220b70bcc99b6a990a56398560dd0bb78aa84bf45e43e0ee4b3e03c5ab31608ead929df2e9fff6a4ff9e9f863592c471ab12d944ed3eb4ea10d80efc7ca22294b54bccc059f04170bdbd6d0a74f2366c0d26b97f0c508b3bbb913ce024b1bd3a5c6ec5f0643d2cab3cf78055334bd67e065564830a42c75590bdb5484fe758902ab79192255c250567b761bec6c6605fccaf50aec508103ea028065c34a799b208654a4b5260a4ff2ba8100c39ee128eef57ffbe36e009e530fcf215176184f956d875eca94390fa1b3b264d4ce4d1dc0bc042596e4da23073a4a6fa4bcd2d95380ceca4b4411a5d3726f3e41e5c8c0792ca5b378414e3044df8fe7074245a610c59c8a741a110a54807d172504ba9a0c078a88c33f610f7ac28e6ca0399fa8dd0a11c4c3bedf6ac81172dd7f6ee5dc6aeb9aaba4f48e0ffd604df818f06c09734a546f69661e9d0d544c7e4477dd644aa6ba9a243e5f6c941405a83216b2cb76e1e58cfc7566bafb11de4025cc40b7c24e0439c6ed791bc794b996e7f0473da9a542ff4aa68c14f224400588b6e4337db6a78558a89ac54f93d2cc076fd15547f1f7618d738c63217e7453d861a7141019f75cd7ec5a6c0c8b690290ea3d2b61d142cd4803a3cd36b3b0d0b4ba545a454e23c14c723e7f21bc1e9d2b571ecdcd21a463a8a793e3013e211a404d414070a1aa635c35e8c8b87", + "shared_secret": "ef4fb9e654c1f7cfe66da7f2d0ed39429067dfdf3b65723ae941221177f55552", + "suite_id": "48504b45004200020002", + "key": "85147d20f1ff72eb9a5d3de9a3c920ab0cac7b00300e6b07a7f53b87ef07e1b0", + "base_nonce": "75437389e6da148fdcaa309d", + "exporter_secret": "2bb8e6404f0df42e403505b7888d04bcdccf4cc33a93d90cdcde8b3604b5278a38aa272e5ae8aa4a0a8ed96eb4ee86f7", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "9d16979cb9ac997886c0ec51ed2c049d7ec53b369467026157ef061af23695b996e1893afd2173c310546859e82eea9c16e0a1363bc994f2ff708e5d60089c1b233f38ce6a7fbd176744", + "nonce": "75437389e6da148fdcaa309d", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "36ac3e4d4b5709eb863f6cd257f046b2f36077a010952a9e2811494adc95667674880e672d9cf1fa4e9e55245d22ca553c86a60cce2714108ba52865dc4addd1025c69b3206598f78903", + "nonce": "75437389e6da148fdcaa309c", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "4e9c9424c210f9cc0d2dd090bb44a022de0b52d3e475d6c4371104f2da02e4a5bc40e993d71f13e36d0b94a730e62198bd73195d688e68ca37dc4fc1cf6f0796e701ca7752204ba806be", + "nonce": "75437389e6da148fdcaa309f", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "964fe9897fa61af5c81ed419d80bb861bc6a341e2b1eb727ed5a6ec7fb7be588e2b631d96228a8c6a56377a26052fe519dfd722201fde9575d81a78b421412fb94d5cbdaa02c9db36ea3", + "nonce": "75437389e6da148fdcaa309e", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "2eee98e32839053d1802c803883f7e75348c0a12751a93614ab5391e6f0d727dae9ad2727cfe4687039e7dcd090eeae23f423e8a39916dad79d4e9b2cc090230e30dfa0d7f86ed2ac4ab", + "nonce": "75437389e6da148fdcaa3099", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "fb737341024d2cfdf2ae4bb789101dcd4e5fdb66ba19409c2af8e76e58fd3478d95125b1ecbc03e7ae98c160fa5882920145b0fc74435ad043892e7b6b633bbd6758b15ef96ff66b29b4", + "nonce": "75437389e6da148fdcaa3098", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "e7f74979e287070650606025e78fd944f43f8f0396d3532a83ff08e16c214c57398b2500699e0642482d0eb312d372d621a41fa80cfcd1eb79083cfd5d426c76ef92452968b2e03e095a", + "nonce": "75437389e6da148fdcaa309b", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "732c8ef9e28903958588d5b92504dcf62f2c6586269f5deb478c3a2385dffcfcb9e9ec5a4d64dcb36b902474799ea10639e404b895c6d4e8d1349f37642f3cee2338cf6cc7b488c6ec67", + "nonce": "75437389e6da148fdcaa309a", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "437ab31d47ec074810331f8fd220e5b2d0fdff12c74747234ea970ce33e92076bbf966abbea347b4db373e4b2d88f76384c707f44abf65154575cbb012dd337c63b68b7bc986dc1f5428", + "nonce": "75437389e6da148fdcaa3095", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "12125f1404895b4ac7f2043071b3e62f354f0306aeb2fb604da6385f5d6b44401d3bbaf55284843b7b3b57049c830826d15121984f11655d6f1807ed1b18e68483b795e254d0174824b4", + "nonce": "75437389e6da148fdcaa3094", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "5bfa8896ed24e61987426ef9c223994f5ea8088f25f6cd46bfed4418a358c352" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "b9074bc3442b61a9d528f26685d741a37b7fae652c726a69f2f4a8d75c2dbfcb" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "c324cc1566312c5ed6d24d96a6c318efcf735828dacd615a2bcfb1a287d4f6d2" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "d5678af261a32206a4171563145aee2b1ff2265a4ca02c2d736c575872eaee64" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "e73be02bb01d7c3ac63ef11e04bda6e3d6a7de507f89574f803236c8c954dc3f" + } + ] + }, + { + "mode": 0, + "kem_id": 80, + "kdf_id": 1, + "aead_id": 1, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "60bb82b558667cd90fdaa6dad511b68ecc36c74aaba659838978c2f1f6e9194fe391cdcd5aaf218bd2132ee45652de419faeff24b210dcdefd78db2f4d80757dc026148067910f470240b079c36f31463f43a33962585e1a84f6230384071be69eac96ecf1ff4b4b4be510631406690c62c037d7ab2623ae4980c5885284d8b610e446eb79b5b6ecbc914ed3b924db036e8604569bec37093231d13bf31eb9df", + "ikmR": "3bf888035cc5f48fa476c2ccdb73a5482e97a0d0578fa710b1e393ca9716b6f0", + "skRm": "1f25a59a6b22ef57b8e48a6cfe739b9ec13e9cf57e82dfd6e0480e0324cf905b", + "pkRm": "67a132b24aba43d90a9ff65c02cc8446ca3131b7496348359647bc145b52f6db1253414e12515a17978a2437753fc754faf312ffea54d6c304f723989681037dfb24b6e89dd72c095b5a2583e8656209845d1241876c4f98093d19d3ca36b8ccdef92471dacb96066dcec17503890456002bc43c60129cc9be2796e74ccb8cfcae45963cdb60890cf35ee6fb51b65c29e1461c89c849f8e3a7ca7a32b3e0318ec8381443950b386200c645fcb5065c0cae32871c18f4b0a587895a78bb7139ad14b238f34306d367b4e8590d1fc40ef30a30993b34b7c380c541653fb6b8f060cfabc149a3440ad61ac4248b162df284dfb9433e623435d64972d9c409f37f4ad28ec20161ddf43ee9d61ac754747f8a0201cb6ed9657b87071e843b789541581db891c8aa60bdf8965582a970300af8cc2a148630216b75d31ab5e3faa5ed5a38ec5c6c459812fbf2412afb96b9095aca552b2572430edb97dbc967ee142df0d0b83da744891a50760326eab584c1859fc42810d7c879fc67b78827313dc01b2a374e0442ca8f20249617b9d5042b2ee140a3c93e73072e9a1163b3d643230466e66871a8b42bc032475259037188c8939660994c900d99011cc83a308a65390827ed8bb5ec85b4b8226e44104b78c09d3b69bc2741cc579244b9763427809cdbd5aa8e838ffe3762eb0a9e23f2c3b25340c799c503609b50313c9f183a04a801ff1b883eb397d370a6c4b05d8f2cc549c40563d16bb01296c7c4afe17634226c95eba58a0af16d0d589a7a0c68ed887d36c98c8b75819a4674a79553f97639e194aa735b04e157127b251658965f141c7319990ebac3cb675a43f2554fdaaa211fd5037938935915c46a01cd0480a9bc624d2555b947d795e142608de987c7c8ae255ab71cb61f0ed7cd0ac88caca1a7b597a2800b15a61222c5f168315985e26378381168a885aa774a6151c065b8047bbf5aa9f3115f55343577782e75121c5d1bc59f4caeb180b5fd1c8811b967db91a5f8a1052adc0eb9941f51328fe6a9608c4bcad74a5168b23475ca1d4be73f99ea59d209bba4a747f589a054ca2854b22924b6bfc07b0a193a7883b2ab62248a0833800bac1759129dc465836fd65721cc08beca917807a3b0e29b0a8192a0a96fc4597177e6779f005be566705a8a2daea40c8cb74b89030600466bbb62232c1a4fb0073257e9c8a518370d97c37448989ce9ce08844cd5114b2db0906892cfac404ed54288796670e2516e9384ccb8aa5bd368bf7425241306082c59bc99942dd8144acf42904cbbb771162807ea07bd53c99ef876398c551a08488b82446ec13ffa257d47f59a96449f1962ba964c48f47130e0dc141b21102a5b003e31945b513b88a887cea21a6f9ab3ca966254db342ad1a5cfc9071c0013f32aa2bcf0b1fb15a22b629f138720394c87b82c779ad5221df26b459a7c8a573743901308066366e370f0480ffbe4a8db234288c1376341aebb98bc9bd1c9c8a702f42c99011c22ce33006752167a4c2aa9649d6661b1ba30485eb35edb34a5f2c82045944a7dc85c6fc8b1f7f28e6c1a6b291591a6d9b9aae515dfb5277fa12d703a1481f44a88d760e6953150c67eb9154362d41aefbbbc2605ff478661dace2516aac53b17ef86e2022546b810d4b22aa2048b134f6b4783ce04cb9f5a67cfe41303954ee05abeab8aab18d97a790e60851fb2f11ab388694528ee86713e8fb3a79c3cdf4852f11c95fe359a2a94af5a55e9", + "enc": "19c8a22f31dcc098ed9a445222584c04c4254c8f87abdc0bc3a308a7c360fe50d133f394f48576f149cc272ea74cc07584186d36237e576ec55fbb49dbf1ec3164ae36675a815460039e17dbffef0ccc733bd554ff7b97fc9db1a98eeb1fdc503ec014ab4cc2d88ac9e1c53ef8796975908365d591dcc16aac61d37d803f53cadcb5005e7730cfa6849a4aab01e07044f69d29ccfe9966cee08b725537b5aad4b1a1e9d29b5061c32aabe077a5161e9a57fb1e8dc024be5f5e8cbed1f1ccfea1d34e302281c325f9b4ee87ad9095295be6d211a19d0f77e9e21ebe1f3ee032759d1a3b8a9589ba340512a0d4b61e112a1c291e0864fec755744b5b3a659920f82971470df89b25283ff19acdbf8ba9b087dc119f7d34f175cb1727bc4539abb277e82680518c6ae1102f5c90bd0f17055b5f21c65be157740daf76d533fa9afe28250a3fe32a767514375f09df494e1d8507a79a7ce4d8d83aaf8addab70feb64a5f1c565b3320bc1ad7171115a050b6b0be8db0447c351e25443f8870c552d074a00b02e03e81ef21f3b7b117ea44675f13c9cf9aa60f5a0941930094bf2787f46c65d314d19d722e10104899bc732d7025174826774e5f355405b2175013b5d0ab4adb980e776cbed35c93d26623ec08bc74a6229c6eb6e476ecf6e31800644589ccbbca7d8c46b138997144d5205e75237df59a0dc901ef3a3b4d3e45616da4761bfcbb7e38dce47ac631849702f66348090ea5a2ebe8e022939f8a108b0f6d89c71aedca58b1bf98b61467fe8862296f1a407dbe11c526b53b175781454563670ec9b7c3b4c062c1af74b6c9f38197b0633e6fb304347b1b31b3110ad463ced8fe2350924f0d49d4cbad080bb4d440270482f5f9985ad16bd8b350fc6f2c6d9d4f1cb571043541901d6aa1f30c0a8595d663a44438f460ab5cdf6504f06927ea71cb35f76a97c732071e234578560566c7d572393d33fb9e6e3401f6006a7cda5b33750465bd0e070b97f55ed540e884f597d3289fc21ed1928c869e263b82cda3cb3a7f06bd28ffad3d71b0a21b8626e0f82223860642566959e3593f168cf623d783189863246910106450415818240f8c047ea86455ee8710c574296f8b698586cb2a067a6bfbbdb5f072483d26c082b96d0a39ddc71cf424a1907d69a1913f81d6c7dfeec1bb7d58d043beb7ad1429a97f9745f8ed207a2e30ddb29ca96561f0699cf3626cb471386674ca21a5a33b009da7fc0152383977bc7169b406571d3118c47fd6fe3224affbc1b8f118deb08e8b761633d96db58054b8fc5e2035c516a74645c323362f8141edd50be3fa21f894483d4597cc046cd3de11b1c0fb2cb8eb0021dad74d404ec952c71ece3f50101b54678883402ee0c3c5533262a66cd1d0784b3fc7dabb28c29d347194521fab5214455117f3f6d40215bfd901a34411a8985ae2fa74b75d61b3d9037a54994622be15ca1bb24a03f6a9464b32d094e78087e69b380dfd1dc137a108961ec564a28d4f083b249c8004310bfc04fa6fa72b58b173345901cae4b54ff0860016232a46bc55d622880aa8a25216c58793cf2c94a1df461b758d43784e9c9cb8e67928b5c81a78903643508659e6ee", + "shared_secret": "556ec9c8df352a315ec7fa6d72848b7f277a5f7181169a107d97b444d7bfa6ce", + "suite_id": "48504b45005000010001", + "key": "e1f50f15239d8c3cbd3fe992913bd365", + "base_nonce": "109dad0a50896f30a4cb478c", + "exporter_secret": "3dc613f4c647d912c18ffc90bc95025efa214264f6c1741587d044cb5a1f3e26", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "c55b375ecf13081a2448aefca58ca81ba771e04bc7299f9152aded351c76ac05cdc985a1609335f1399855f528adb21f48dfcc841fd7ef1c38bc64d9bdcd9c18c68d6d7a6c247429677d", + "nonce": "109dad0a50896f30a4cb478c", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "d3ef453d4ae5192c86d339c1f3ddd5e487c1553018da29de16e08b82bb4c0b82f606118ae9e11967d1cf572f27f64c4c29cdb4c70bee123664981a62169a3ed664f50229e8ec7726d1d9", + "nonce": "109dad0a50896f30a4cb478d", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "fec739822bc47a13b041e2e45720a5401b084bced934678f462fcd47c0494f1f5bacf0cd3417fb208e80b3ca1c5946000f84dd359434dcda5efc36bcc3a3872e569a95149ae749273e27", + "nonce": "109dad0a50896f30a4cb478e", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "682c9e5c34e9e5da25e470e5d6d9952f9316e919f499724625e6155e7b8152682324d09f217e9242df1e0c7502847962c91f55be2657a0183cf35cb4b48ac6cbfeb4218a15145b924fb2", + "nonce": "109dad0a50896f30a4cb478f", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "d0877d09ee420d5e51d9323c6d774733711377f2ebe2495878fef4f342f2f84bd931e279fcc9b37ef85d7c721363a8eabbf7b223114bc99abfc79a41a6a4b929215857de2f3615f1e05d", + "nonce": "109dad0a50896f30a4cb4788", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "c82e8b47990d370b5241dce51efbc2ffd5dcd34f5efeef1174b85c025fdc1133d673fe3d13bc54f68706f276560df8799dc6513af3cdb5c8bc41489449f9eba46dacef38fcca9a1644e5", + "nonce": "109dad0a50896f30a4cb4789", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "7b732cdb3cc76ca747fba1f4f51b194b6b62c16520fab04787d436bbffedbf8223db99cf4fbc57ce1023d2a3d7a2377285b42228568267d1a25005a3a1021f73375791345505d56aa87b", + "nonce": "109dad0a50896f30a4cb478a", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "2e95a888e434c5b820e6ff45492413eebf8912520c1ed53ddb04717ee6f2bd1efef1adeffe1bc557eb7d425cbf6fa5ce1fdda9e801727ccca601ad36e3f1b3926443c7501b886f1ca798", + "nonce": "109dad0a50896f30a4cb478b", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "d2c225d784967a0faa7f27199d185c34965313736f2ae14e5ff93838b10615aec94f5483f7505c3d56b13ff19708b4bb55aa62ea78e78d63b405606d7c04420c567efb5399c71b65d4f0", + "nonce": "109dad0a50896f30a4cb4784", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "eb2ce0435207a409d8016fab396018c3fbbbed318850546be045f2bf81eac4c3938ad9820117440a8ab9cfacea7fa3d0b7e5bb5c760153c99a90c6f24cd25dac1b94b823d2c6e8780528", + "nonce": "109dad0a50896f30a4cb4785", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "a7e801ca7724275eea77f2e95340b7140b98aaa9f0035daa0be6d3325db4128f" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "4b6193c46d11cb047153e27e9cb43aa8ac1c107da4678ed3852ba8415ee3ff53" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "0f1490b86f762d3f7444072ea2cf5cf1641913950a6d81e4312af823b552d3c5" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "9d4620b1373bda21983457c5e2a00b5e3b99beced43a5b6cf434aa9c2ee66862" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "dc1febb6ef028d0ab03ea77a197f28a5b9e07c2e2808d4c29152c83e70bfe9df" + } + ] + }, + { + "mode": 0, + "kem_id": 25722, + "kdf_id": 1, + "aead_id": 3, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "c82228383c9fb887f7d8b332c28262024eda5b6b0ecd2325fe662daffc0594fad4990e7c8d13812137d06ba7017453de675ab0388d418853617f3ca58cc5daf3", + "ikmR": "c8575d137deab99ac98fb0873048c83c3a1f47ef5b409f609c0ca652f58c83e0", + "skRm": "b6bfa0299b955e85224df2e468f29eeab377ff3b96d4462b39447a22d32b91be", + "pkRm": "d3d102410970b8bab2984008669914490c95dea2c2ae331ca229aaf3609a6d5acbd60a9ecdd71cd462c08300c474e318b554675679b6d4e1bb1a76269a7a87e68335ba54593239290f35b65e5aa0d3352933339336e83260125e10ac21c97b40cff8385a18b2b7d62436377a13b00c27d22fea4176ff76ab80970ff0e26101c245b7c33dcaa420da8ab4df41b88985c6f3845e5a5364e7d7a74ae764c175bd462017cbbc5f684a7e4494391d050b83ba9ba66347774c707658c5cfe481a8c20efc670314d7569a4793625abfd6579435b31886f80924c88c9a4011a0c58b6fe85c5330ca27aa80ffcc58d3ac6a759897b44893bf8b520802b4d7d56bc24976ba32c070f0cc7fd88c19e958de0b8fe93a7669b5bb8cf80a88e6aa827924cb1659fe6177023611ddd30eb913ad8b2c08fc8209bb9719c61450518881ca5b12b8c73ba3c27824773b5ec6571df41b277555d9aa8f566444614a8473d3b1a5e997af066ae1f355338302952281c82ba94f50a22e9ba55a56193d1a3f4b5365f7eb2ca81cb4573c33d1d9861cac8d4cf327c0393ab0c0b02572bbc3821bedb45d40257e1f7643796786fcf50126eb136b35737794168a15643973639f6b6f3dd945d3ac6a5ab96e5d4b8fdcc54809053828610ee3a56bf4c84447147a9ca5745d5c05e3d9cfa659bb4c4c8f2e006826c27633a120c32151deaa2a98bc9578232b518604a5186fc7ac6a1e594b09106418eb1aa1ec6b44fabeae5105d75531b9279b07f6277ab297a5ea77d2383154bcb9db8a08fbb10522987d63c99958a774160333f5f208567a311464b59ebb8cc8375a1f02470de68bad55c8e5184c8c4a0dcd45bfb59995f4c48ecf947896126830818aca48c0749c057b6591d381922d8bb593e09d4bb7b6b046179c16a1d6b4149fb1cee31b6b9a015cca526cfac8bf060c699e751c9456c26d832d58926032bb78ffa44d0d024f7a417e1b452f1f779d49c86e0784ab97686acb025c90f9893cc13bfc9186f0e9946ba498eb15a6684828bc0ca91482ce91dc4c12d0ce4b936e2556aebdc48cede6adcc066e78895086b49316725d9a0987ca75a7e00b0bbe909397389fead7a8ff73bf94bb4272b27b9354bdbf3777a978024d074a45077ed0435b8b845e4481139b39ad1276973fd1cd8b1cc6e27b16998a7d6295c98347998d6669b15270dba7c098f16136015bd3b1c5897b1312f230e49bc07476cf4deb5bf0c971d5ac37f980283940c4e52999c549aed09681a7f350f5a1bb1dd5a3b8837a22d506eb2029146817ec955d9dbc2939514ecf468c4eeb85198230effac612fb73348b42bcb9b27d9816a16596fb3618e71bcc321a4523a66ca248ac032c06b9e24cb084c59bc489868639ae7533f8f03a4eeb56f8a3568f687f38f83fbe163673eb76275b79d7f32a9109c30eaa96a0f622503c0337801fd9b2007603ce73c0378b250c35b634b6c8ac2cec1fbb24561a7a7812e77b4844bf9c6a299b409303c85199ca5dc6eac4e4f85ff10a464d96c71382760af67fdf006c4eb6b627861d5bf1978588c02825ac70206274742af05a0dd136c2d465ac90b4293d05381d5911dcf48a878a6cf8d971d62c14700867e91dd050f85d68c32e227e5be68d3de029608179f6f83b95e5deb6263fc01c80641763ebe08e7add5686b0ed1d1e7053982aa616130939d0a09462df7dc74d05", + "enc": "ab354dd589f74ee0eab7718a630cbec5df1d09058e177cd6dd141d883450ddd70c050d88bed3d07cce23415cab411108cc30906482a71adcb134a56e978a6152a8e063b24acd1534f264f10458152a9ed4f1f32b3d480c4f2453b7fdea7720146b3ee92cf8a13a4840076f68c911c65fa3db5053fb0aabf79e64cd5e7aa71b2b9641e713ec7df552e17d5020f8721ee449b42c888e2a3f87cfd96e3a98c3e7c4cd8f647f899570f596bf17d2b6fa2cad19706d9cc3cf09493e1c7ffa0eb2a4559ae1d940fdbef97bed383e6ccfdb448d9f1a81805166b32c2af2e16878c6dc46ab43323ed9c136b925239782e3c329c31a5cf2a80faf025a80766e244605c27afe4b624d9d8ca99b6ef5439ed1ad044b518c434385acd49f1369ded6624a2832a571ccdd70d08b3c04cb1cd3136166f9a485f536f69ec66f0293e840025ccaac42f8e5f7c9cb818076c272797047f5e50c1e9f1dab81cfb48fe4c4998b2427f009702b145f34ad8dbc3e7ad4e4023057ba31cd02c4c0545ebf71eb02533e8eaa2b2f2690ee1407bf1f66dc5f4d836c45b82f10b720df72d237488a9af1b6dfb4741fd613379c2e211e77f7fae6b3734ad81de2d452005334857c4a3cbc82afc7428fe510495969b296d24e1a7431f557d48578cf92ae86c0392f0ba73755a9e5465c8e3495e4cd2a82d463244341e39414e26c9b242f31d2cf0e46b2aeb11dd5e56ec44834350d151344229e410faff2b2ace5c9b3fa12571db1d28da2c7133492781dac41b7a7e2bac2260fd12f56939033587824c9dfb17d41b3bceea53763193abe0c7c184d5de161ef5312f31fab42478c9a193b868e4d29b2b7624f3ebe740f393d03d843cd5327286a579fd2a6e37aca5b64f9316115d612c7781e704ea7d182701c5019975cad14fbf4ab3904d4a35acaf0be32d716a1ef5d7188fc418ae9e60744325a3e8001655b756df94c24031c3ce32bd90c0ecdac52ca140fdad7f44d04bd0a7e2a726c54cf9793f8784a23296f65da3fd1cbc18300d503c5b27be99b9b0e32d20b3614dc8a999f30c2779dd7886cfd486dc1c93ebcf517b5210a4359d9fa1805381f0f2261ff47de01de555d98bc1a30dda557a83007b61636abaf9041f96890f0f565eefc45859fbcd32d91b203215541227a4fcc3d95be2ddb0702878caa20f2da62c4ff9fe33af591ba1ec241fbe2208e0480f8b1cca1679c096f8f5a02a33e9df445b3274ac112b43d51510135cd3f532a3379e90bb7f0cb43717e90555bb1a80924cc69577455687cceb9b1610c05839541e87ad83d79ef3ff24ace1934cfbe989691959d93ac48c716b672b370dd4c144ca1e32508707a6ef8aa29b55759b3d054c56bee1baa6f41b84b9fc3fd681a1a1528eac578141529836a29dda1501a49ba2455367256d2fe6f74ebb74ef9a49a94a4c6cd1dd09810f0e9bffa69dd8c94d226d0b2977b11a35382888961004a44c60fd602e9ff4271287e9240ba96146515b9db9da60375aeeafeac1eeb764faebacd197df27817c35fe4c5c802e43349d7bc95c8b40c001449d3251c1d92ff6d5c3b08c4b27c", + "shared_secret": "e059d39125d1f09a7232413a13ec5cb18a37417675442c962700d59da46d105a", + "suite_id": "48504b45647a00010003", + "key": "100a6260ae1a2660dd575fd4f8bf8ebddda22b8bdb0f1bca9fcb26c0aa12418e", + "base_nonce": "b55542ef5c7fc75d075bbfde", + "exporter_secret": "2838515658ae0c04e99391c0bad9a45613254bad20f7cdd80a70b25f71fec22d", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "a4ab74475a498ed725f685421f67c09a4783fe76f67bd251e1e73db8eb1452dfad4df3c6453f7edecc7bb055dde561e2efd54d73a3d4f1f2f02eac90ba1e9b84ded66d43aee6393524db", + "nonce": "b55542ef5c7fc75d075bbfde", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "9a38169c711afe09f0e470779b137ab236a2559faf61d55e417eaf06d48fe62364643e95ca68382a281c04c67bd88f311939955a14f5f8c64344e4c103e1d94f3084f122671fc7c0cd7e", + "nonce": "b55542ef5c7fc75d075bbfdf", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "0f2a23afb6d94feccf5e151cc5cac46e686325dbf34def1394d157448c420376cee6d3c8aa4f3862edcc16fb4c3a9ef8a7ca1054d13fd9b1cfc4a5a47198ffb3b97372295c894dded976", + "nonce": "b55542ef5c7fc75d075bbfdc", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "e5bad2306fccb51b100ce45fba5f88947d7853d66478099011de84bd2a5b9de337efdf9c459da7edee76cd07db9b7cddbc9a512fa45ee8f121531660861818ddda7d9d2b53d3f257d36a", + "nonce": "b55542ef5c7fc75d075bbfdd", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "923c933cbdc5955d34bf6134f1ad5e3c36f2acc9d9fb41dd8b4e0eba2101a05ad5fe7a895e683176381b9baba7ced24498e87766a14fec8c256c7df2802371a5d29d2faf03d9b4bbcaa9", + "nonce": "b55542ef5c7fc75d075bbfda", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "0dd329bc293f83ac756c3fd052c2e52a8082a0a41069764736319127fb222a644f6c28155bfab1bd7763d34ab09907ead4f497c9b14d729c75c8bb6f4acfdd596f39c2e9872557802821", + "nonce": "b55542ef5c7fc75d075bbfdb", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "ae092db4f524c459e04a2453c92b59edef2ed07d08138f3582e7cfcb735d23772aa223baad29cc2dfe2e6d2935d7c593828413eaabd7eb6cb2b20d6e9fb82ab521d25f80fb93190260cc", + "nonce": "b55542ef5c7fc75d075bbfd8", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "299434544769a352047b82f0f74cd7f832dbb47b8b3d8500476f95e07d13502e4b748296f19f51c4b35f4c8f80607d087f6e0632e68bad401e4153c95ceb380c9b6e1a50477069d68345", + "nonce": "b55542ef5c7fc75d075bbfd9", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "6f09ad8f28a51f7f3e34eb99ac25e9b1e4a09ca063babb7c462a7ac7f65310aa2d4a1eb3b629fba0606793c10a469af0b2407aac02e6073fa8b070d9aab532a8349f884fd97cc5041b2b", + "nonce": "b55542ef5c7fc75d075bbfd6", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "90eb1a4fc6e9f6b5cb142d60d905ad221135aab544c8a20484c249d5417bd2eb97c655ba8f15feda2adaab1a6bb5c06d0d7b17a6a6d298c9df95681d5ed131d7c703918397e302620c6a", + "nonce": "b55542ef5c7fc75d075bbfd7", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "14b64c194571a8e6fc16cb4d22754c79391081174b6001cd8050142f928defbb" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "7152f82449d4cd8137afb9b514fab6791e0938ba7734cb62228b8385003f32a6" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "e3759bd609d81501bdac23863795a37437e804dd3274f0ed3964f82e8a6f97e5" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "3e9e46c25af47fe02c149de2d3d8300881bb59fdb5b07c6b46d001918945cffa" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "0dc9bd0216b18caa75d0188cab85d7fdef4b405c340da75b50e052b889fb2f78" + } + ] + }, + { + "mode": 0, + "kem_id": 81, + "kdf_id": 2, + "aead_id": 2, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "a2aa5d3e682abee327d4d258e47fdf9b987efc96a15e1f11fd81413206d1ae2ab11e0d808cb65a680cf32b00eed796e02d149f3454974db3e1751cf2fc1916e0d887c307c18b28645809760d00d6191a", + "ikmR": "14c036a5e3c4af452baccdcd62cf818f250607076c299636e5c8074b3c757df1", + "skRm": "0ba4a1ff718a4444da0016d59f449e28d8abdeac107ee105e5ac0dc1e8219b37", + "pkRm": "dd5407b4430e39f3416a6bc8e7d43b2a72c4f7480b1fd19e0c552af07b8f5bba626c2c462724600d3b1598a254fa287661f14d1cf53a08019cb352b7078b2e6da44afb80467bc58c0b016933941a386609b66c8fecb0802ed41559dbb80ea5773d3c446f60c103d121049722f7106658422b1a074ccac083e3503a70a259ad15358b49b08f7b6502125cae1c18f3e596e4318297b3634e5a57a1275b60499b6fe88372520f3ce62e90d36300c7395769227075a796943e9c257e213791a9fba955647167951bab3b8fd1a6baa6fca016ec51d59caed7bc45b77c1417ea1ae2662e96d2338cf5a81178071ea127891788511731dd450b60c125f3cb10a847b9e9558455608271a3b26ab3a996a697421441e28a4cc410c04b35379c04098f2415097481fb1c8a5e25392c56130e948c17d59bc0f01ac9353359ec6d5d19cbbe75589033717f797fb967c01877272d4509fb3c8259a13dc7d78eebb51c152bab162b5259f0a36e3c2f8468ac14c279b65929070546f3b572d2b84324943c227011c878533629c96a235b4d88b498c0bba890535f00c98c01b620e3c5b293014d2300f07b9d6c6a8e4c09c05532b5b4017099d1262f299f7b92663fbb074d115a29f7a7d56b366a6cc48c931b19d12cb6ea28487a0c27d0cd5df93349b8c5f8d5956196543e429d41f82d734c8dc5281d214c7b7ab32d9dba2c79b0007f229ad1b02e91c3ad1abc8d3ac16a9b951610d5293204522f4b049c77905115418ce571c1ac43906799a2898f901105efe93c5acca2fbc72fc61a96a13b6869931009da8b2f472914828908d85fd4806fb12a28b254b8f600c9b9a41ada526e41c8719ffa71a7638c1ed4b4d86972e6da7442d98a5a9b8c5dd69fa5dc5c76a11f2a439436c779b255604f2947205434cd8a155852864d009fc062623f5c89f3c381be153e0479b032240e1c762e67d81728993f14431c8b0b3d3bb9103cb0262ef34d02d9a3cef9bd25845a392320156784adb54df0691428d7b290827903abaa8fc009c57881a42c09dfa7bd9ec8c3e234b9f107ac00b42964266fd615b25bd906a1c762a9f7678b56af6b9598e9db1fd1573f6dd803885b3907a0c4d618a6337b29f37c8515509755e74e7364a6b32447cba852aa396ad9863a8d444aa39b519855bd8ef40488776c02997033a4a6ac9520d898972b7aa49edc50d4b9143197974b1b3e36668bccc0be1754a263ba63ada761aa852717050eec3b36a46835828569c527bae503725679654e652520c5369fb0b862174110d5b36cd5c91d78c6d49752efc5c10b8a6dd51ba20dbc384cc20a30308912644090c8726536a35c906a75247663798b94a3647de50caea55324825719f99d2b649e7d77be5e242a3227c89f4198da3c59e81c5f1b999e5601968ef5b1ca6574b2501c33383fbe92ad8ff5a2ba98b60428153b463ff32131c49b6272681dbe2299e8d292a163b903d7911dfc61afa104e01433c2ab322b884743d338be8272e11561e51a4d53d371daf430d41c997192ac2c27197385cd5a373bfc3ca8d6db042e16076afa6ac31abceea7bff73915c42acb25077405438ba8989ccd44b05d4043f8a3217b05bf899c8651bcc740ba9a678c6b9bc9313bc21a8a38205e079d1051a6bf1071f6a0a38f3a0db88a7c9415cd5af72c3dd247bd20b14ea0a1d988816f101e302c9bf3918839e68a4f19c621e446940305c0567dbb2c8f6fb0257d09727e164a021b562a1bc2cde1b064dac144ba4f84f14ebee5999db03b2d892d629c134fca1457b851b1891f055437caeba081814b029672b21c9a4283384bc93a8579ca5eb99067d84c0e10c3b5bc1d7311829f95458a0b40e52681b1c262112b6c7f374ea86978359c096dfcafaf097d520b10c021c2d0e0ab9dd8570d9b3f884448379367caabbbe924478f72b0470c7c1ed956fe4b791db29ac9b6b905a8aac1b904fff757fddb27348932a6560f4b776c2a74510b11379800b538b27114715273f43f9f7acb63b3addf301966591b963979028449cef3802269c8f94282364546ce91957b509c5f5696cfa49bb1eacae0219c963b802b35986a0b857c0144a9d1cab7d29d851970bda11b2c86811023b216d4930ca94f97d80b7b65a0cb7a9ada2b37bde4355c5b65c5ec15b1eb61530b04566a4299e91afbef01cc121f19ab90b705b5bf2e57373c16c68604a70238ee7c3f7e7d364957e5f4e32fa6747c811fb22b7efde99966fd0da372510c6e1fce1b25ce287707e5b3b0a2384e884cc6a3ac281fa6f5705d09ef0a823bfed3af81d8e5a38bb914d2269de2a4e47a1d7cdc6d85cbc009ea787f2eefed4b", + "enc": "6252bc46bca0a8fea250a751deef5ebcd053d86881cab58afe159028253fa5bed2fb7eca382831b2e9a0714629521466d6092509a0892e93d927d177c9b0ccfe66e2fa44f2f1426ce7148cab999bcdae2e3db25ced0d669c078772346cdf7b12fca942f5ea27ab175e74b861d1aac098384e848537627d21b64f460e008b8c5a15c6811c892d49a053f8a1c06a8b1960b4650a8c7f91ecaf50079e34e2aeb1bb45935cd4b578cb7a2578b2cd4215f803a02353d9bc83f096e2982b41e9e089d158b4dade7959915d2ae7b66c9ec4aa9f5f85faa62a8d4cedaace187eef5da43ff523b4de139cfb7ee3edda8d2e45af7b591646920836ac97d83067a5f3ccc9bdf6b10958b2542a600dd5e27d51d3a3179aa82260b272f3580bd76c19d6c7f9960a04d72197904800a35234b84c50c142e68ddccc5a89dcb94491a1f03981bc1c4d033f48fb18b4da14bdfb64b4e2e9985f21d634e3a4a88ca9f2782a2f11c79632e23139b2c26d16de006c211f09493a7985e5eac0952a65449ecb84c2d0b7c7ee27c5c127851b9b8061f8f9c64d6e98650bbe7321a2fad69fa6ced8adffdad8f40dbd7122406211c09957d37eeab1721200abf815e66b0afa5d2986f66afa9b80bfcd0bbdd6b848a19486f5a2daff4793b54d0a1ab99593977dbf561959919978f21d6b924fc19cdd54572b72f1f6fb4f765501b955dc833bf627684f367e0bb02232ef428bd3aaa20aaebf36861432f6b1eaa022f5db22566d0065cc78f2059f777ded29c2f7218c8995a988fc81af98b9d97551efef39b72b84cab58154c903ff724959d286d8159d1a0aee218ede82edb148286f7ff8fc8ec4a8fb48fe912851a3a677f6c27529edb36d811402a9e5658aade9e91df8c13765e41aca064b14397613426dfb51f7971c29d8d688233a4e3a1e6e1c96e1c1ec39b4d2c0fbc5258e1f363ce11c803183e4af52777ec4750dde7d499f4d8e1a69f78af8e3c2e75bb8de85376ab29d7f3da499a8480196258436386151b57252d104a061112721b73ce1f2f5bb0334fd417d88bab0fdac368f46b2db22330adb6cd8e747dc14eff8cef6353c94f9525f6d0c1d32ec20b7ec624ad8df4b5e82b72375bee995fad8c9694e765e2c5ff3d97e9cddf8848618bf08c7680f1a9f2cef663f81ad95f8aca6855f8aec99157ce9758883877326d08d75872a549f524cc5abadc3b007f9ad37072376e97f7c7997b1548dcef72ebb751251a1f499c6d79bd4a6ce83331d449aff880e19473fea5ec9387ff984f24d56a7dd58426af98203506a7d7e8c00399144e91d9283e4cda4c3d7b7baa58bd7b58028101e57ef0410eb6bdf15aaad25949f0e4e3610655197a6b6a6d9941109828299c567cb68bd18e2359552959bea6ac6d51c181caf35b0f5fa0a6b075c3309bb06ff3bf36c6110241be25bb26b5c36b74059fb0c72a0af36e65e8ea7cd4836f79931fa72b0606941a7f1474cab150b90ef2f7bd69b994e128177f387e6963dd7d5c15899230163b743cda48777fe95d64d5a61278e2375e77a556e0ee52ef944e36a141123d6d03ba39db4481db40a545aa14b91bb14663d717f93d2db3fbf838c4dfda0ad866b652a1f2dba6becb1f856e1583305447824396bd2c8eb7ad02c86c9779aec904e85732141bef525fe5c271ccb655e7dbc5f82327971905e9e8c52bbddac260500a8e7667e2069947da3d62405fa357a0a96a937fd6b6ab9b0fc52fec997e63819fb1666db67429fc2971f8aa53ff690877fe1b4c334a82c416822cfd06e2eb783e7bc20a76c6596990b12f06e3a597764e2ca85b14f511e63eee821338d80451d714dc8fb2f3cc9a5077553f121ae5edc0ac2e37f70e6454bc5bc3582b4da9872fddde5a0abc3f981fb5af044a78ec102827bccdca891218faeb27b0ccde8fb71f0b32dda854f737dfb7811c386c7d833d3bc81952b83b964dd61464477fb50f86ff5eb5b6f3929928ab7cdec9974cfb97086fdf21ae4fd0d137ccb825d45584b5cadaac383abd8d8d7b97229aea44e0985db277fc8c38bc93b520dfb197e5a9106c48e903c2120e12a710261db45a17d41342a4053ce23b80fdfd90278dc64e0f6dc794d3740b34a28041f00a5b70e3c1dcc6e60944fd1cf6dbad0907c55b5501cea7acbce32c02066dab5a7f3bc2c2c237689b0299e18269df7252eab5e543ab03a777faef62f04d1b38e73e0254b09c72a40c7a1e073dc3725a32f5d9de0e9de45d907b4cba48c3e078b4dcd78668b3ebd5c67b1682aa5beaed5e02473d713ff3181acd63c98fde2f301e53c92b751c7dae053d7914f5c0c4633c0b16377c47fe22c64ec4bae84", + "shared_secret": "226311ca7023793ede9bd9503137298e036add770ea5a6c46efbd17e2c1a0855", + "suite_id": "48504b45005100020002", + "key": "26143789a8c64c529d174ee0a614460bdefdcb82dfae5eb82821deb7bab61dc8", + "base_nonce": "b0dc993388b766c96e7a8267", + "exporter_secret": "5b92680d4c918985d6184e85b2696079047c2ecc21c19f58ed7bbbbed68a203720120ce34ae2dc8aac2e992b484f3738", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "1af5c6176d191f913bb9a39ae6af2c5847d5effca2d794242de5464ef287bfd6d5f6735bab1b42b3d29a6b131a91b180b04dbf6afc395bdc35f2b8558db9c62ce54c81872b42d222459a", + "nonce": "b0dc993388b766c96e7a8267", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "9e34298676cbe51af56ba3dbf356292f35189305f123b59f1fb4825f4d1746d84f4440ed957cd610b6aa0208956c9664a8297751377c909160df88bd33908f962593333727f83766f42b", + "nonce": "b0dc993388b766c96e7a8266", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "c2e427b4fc917ad8fa5cc2c2d63802a287be09d75e3c220bdd802a2365d087043058b6bbd64a966d51326646cc58ef6e0e5a4057f2082f305d96d9017482d292b21ac25ab2bb500f2c9f", + "nonce": "b0dc993388b766c96e7a8265", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "5b2f693bc6bbab8d63cb94945dc02143c07a221481245d29f24d6dbbd44a3a61c980c41a593c1fd48510d58b7cf820421322925d75232504ed20757ba1afeaca228f6c6f76c1b8cdac00", + "nonce": "b0dc993388b766c96e7a8264", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "306c0c5442f3257c52949676ca71b7eda004815a71dafce837df59826ec8bcb269fe7fc394bb508b169143f9770e07a27282fc32a0d4f67c3e5cb4a4fa5aba2165a6826148cd71e9abfa", + "nonce": "b0dc993388b766c96e7a8263", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "6dc0fcc3e2785e176a8424a268692c1f3ed979b4f916b84a3b8477467bf5061d1a79556aec380cbe2ecf5aeabbb7b2b7863a3336c807e9d2df1c7c3673bd4a396bf099b2ac33b39393bf", + "nonce": "b0dc993388b766c96e7a8262", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "38c652266fedc8d386d700e5209de8a2043b08ece96885fd07b3c630cdbd1dd422877a5ae9b47778b0c90d4dd36c2f6bc9ffc4a2dc734a577c0e11d60b47f8f2cd37ace2e6bffb87cb82", + "nonce": "b0dc993388b766c96e7a8261", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "a22807c89d25b30fd13d869eaddd17846c44c225bc6c941a3f9f4310a5d101fa02d3b13c24db96b3744ec7ba7a81efc1daab7a5cdc1aa0ea4d95abf359905c72438a4af4fda27a581970", + "nonce": "b0dc993388b766c96e7a8260", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "838837f830205843e1b4a5bf12d39ed31a4879af7154694088de2b8223c91fd776353f110bdf9d34d9e38c65554e22ce8345b7e4e697d1b9db9a4ffe7e35a0058f062bda1051b008fc13", + "nonce": "b0dc993388b766c96e7a826f", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "2353b5655181723109376689bb5683afd959c8ba50fd43abeafff0a1d04943c6a7177ead11f09a74acf0fb77353fbd7b893b61fc4dab0b71a944e704ec84199fef1fc07bec4ca18fbd70", + "nonce": "b0dc993388b766c96e7a826e", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "29c8d4342d91ac6b7be5167cd58db0d6f0db21356c4dda73964e0d1bcca575fb" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "8218e9f4d94056911a6e0b46446ea36b02f16ed7b8f2d7333a153dd7d914c422" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "9470d784dddfa4c994942dbbc6466d7bf557253f1055018a7c0e7c11d1f91b19" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "d3e89c0a3ae9836c5de623e4470de1cafab694f2fa17ebb144aa2f93d9ad7491" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "1909e2bc8d6d98ca41f88f000c0cb0722160a4263cee5e1bd5817f276248db54" + } + ] + }, + { + "mode": 0, + "kem_id": 16, + "kdf_id": 16, + "aead_id": 1, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "6695fdcffdc5e0198bb336410fd8a0449c1754bbcdf90c03397996f61477df31", + "ikmR": "baea9ef03113b6b3eae42055d1153824e0d6ce292c7a7776c46164b3d7ff472d", + "skRm": "940a1692f2c9bdcc71c563304d019359c08d9cf031c97ff731accace45298abb", + "pkRm": "0499c51fe81dd142193be7ebfb9bbead8da7c5014364f07d70b6947003b037a77d1d2ab7664e4456baf9ae18617731c5217ab5ba724df2c6ee06e167d6f8ad3430", + "enc": "040d6b7d55773a677961fcd20a94a428cce3887a0eadccff4177afae894d13457b9a6c6ace3afbcb3a8a7b6dcf341fad4f8c4a46594994765a493123ef00564eb3", + "shared_secret": "aa92abe0c252ce7357b0c3eb6b31f8e5934bcbdcd5d1291dd0ca238aa678244f", + "suite_id": "48504b45001000100001", + "key": "c7a6a81a2a59761aade2149116f463f1", + "base_nonce": "66429e34404232db6ac64888", + "exporter_secret": "4603c7eacbc8bc64150037769c56f246b2473dbcc1a73775ddd2e24d0daa19df", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "b6bbe209cf13d2e491651b4e01a70421cb63f509c4f54b468338ebdc9cbe09e5342145c1c367b1ead479b804823ba1ea640df5f9f7bebfeae4cf596f786dc4c80acc4ce56e4ef72e53a2", + "nonce": "66429e34404232db6ac64888", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "fd31b952b731aad4f43597b0cc6ba2c3a3e56f78abc201b86bed80798c5cb874d14dbac6f33c8700d0a629e1267c76ed6f101b1326c3acdb125c7eb6ead45a3148b86766d2ced80e2da0", + "nonce": "66429e34404232db6ac64889", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "d0c5f0d1fc43c8644600dcfd667d7cbd899c6c68f1862efab6e8fd6f2559b2e486a4993a3f83edc83c17c795709ec0192d58593983ba2c47999cee42c78e61c07baf68824e9cd83b51a7", + "nonce": "66429e34404232db6ac6488a", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "391f695f38e28c44f74a0f852a2c66b910b5deffcb9fb745f0c70be788d49d16532ddf9242e664cb47e8f4060a0cec5198bd79bf3cfb920a66729774d32039153c8690756b0eebb2eab6", + "nonce": "66429e34404232db6ac6488b", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "2b65e00a42302b5de535b58860e3c84b71ee45ec21136a0c016d2308b2191a886c3d988fe07a00e7d155a18728816c2acb561c898b13797cb3a484b76a8e9bbcb3bd189d6e1029427c8c", + "nonce": "66429e34404232db6ac6488c", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "8676265f0aa3641bb35381c51d49aa1ecbb91e0627d327f44183ca72bed04cd86e7d56b633435d32af3a0c4feb39b5a2e180b1aa5571fad5c47e9ae1dcdf63fbdd7a48c6457a3be977e5", + "nonce": "66429e34404232db6ac6488d", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "6cea0b9099a85ccd3f10f8ee1ea90dc831dfe8e98a26e06380979130f46694e6dabadccff3e93f8e77473bf217161b5c2da12d4c11c28657a157a1a6705d5e6af6b27d907cc623d25422", + "nonce": "66429e34404232db6ac6488e", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "f5dc80fb97c2e2d59237ef7beafb807d39504918a2fc9e6e35fc7be42ffec4f1ab4f4328d70725ad9e660316af5ace8a27589ab8d1fb7fa4758c4c39b1797f4cccf7dcc6b521f1548f9f", + "nonce": "66429e34404232db6ac6488f", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "5e2bceef6807a069ef9aad584eb22df76ac154cdce7e29d10d9cef375ff665729b0b92cf9b1cb60b87edf4c6098528870a19df9300cabc3b2b19ec5cfd269364018d76ebd92f675d6de1", + "nonce": "66429e34404232db6ac64880", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "5d407120f1530c82be8bae92db4b1974726f73709570d7067efc1a9e6a34d9fac871edf34f78130b87d92e87e3df592e147dce627d00b489236f3b3adf7dc9c67fd3b8a489ea5f926914", + "nonce": "66429e34404232db6ac64881", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "33367f44b8561d2be9a67535926bc2f52949267b70f4a76d9294c69056196ee5" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "2c07c3d1d68ef711c380700bf019bab6d88616b39060ced822c666ad0dd679e9" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "2fadebf4f18368f7d5d270562daa449e31c6c843e87a21451667bdfcd016255e" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "a0569fb14b7487e35630d680c1dd8db0be672f86d9e77dbff86217fddb601a6f" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "45d7f2b32afc6d8f5e1214bbeea6c43c275e0b82edda762171aef9f055a42fdd" + } + ] + }, + { + "mode": 0, + "kem_id": 17, + "kdf_id": 17, + "aead_id": 2, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "253bec4623463f09f36b496c419fd8830fa68cc2981e720becde042c865ae9b6348d1d2795be4e23d65c19523fb99f21", + "ikmR": "65fca3ea3b6db29a62bff28ec53c08710fab10b3798e59b678d3224296d5883f039123471784ce57b0d85a17cd521196", + "skRm": "679172205e04663f40fda1018cd46c18ebaa876ede6998ba86b051614ca4d5e4bfbea34b720617a4b958cc80f6305244", + "pkRm": "04a5f53da8564364255bc36850df793672782a5c9e4a7fb5fb2e2146eb12e4d8477ab1f326a361dfd1e41212109510e813380547c68c0964c1908f16f67b902a061be27b2f8b43f1fab1bf0dbf89f5167ce80aca2c210b8fc0f040699db9ee1229", + "enc": "049f1da943827d165268869c842962c1feba1fb46402fd3fac50c002cf44bb103c1aa8fb15a848f9908554624b0eac4573ec258788335421dcbfa625bfc9136cfa0e335f0de018e4f9517ae0a8863f1b3631343c49c67fd240213f86af1b235ba4", + "shared_secret": "f609b68f1e65f077d9cca41ad41d45dd66284adfb8341b9ebdd0ff39c90917a1af423d5b70d6a917ebf469e093023850", + "suite_id": "48504b45001100110002", + "key": "4c314eaf3ad5fc2c6ec5478d159c566a209c36d22828e8a51e4c84537cfb7c5a", + "base_nonce": "77459442b645123943d74d7b", + "exporter_secret": "a2c1e1738982407a75c68acffd70d2d63cc3f753ff437947e56337fd6e612d09a6f776a3628f236c91c2b39c0e30ce70730bcf8379fabac484540eaf89cec1ea", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "3c7922016241555d76d87b725f17058f9c309cb3b793b3d8b503cd99a6174130aa6fc6792f94345bfd5e8ec4cfc3641bf6a672b5285598e49dab91ebd71c38d703d4e41c0c6cd23b8cf7", + "nonce": "77459442b645123943d74d7b", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "a1745c52e73ed7c7c05cd4d712094dc5c3ec84d316a82ec0e338b64dd11742f42f7b2b391cd0d3397d451ed48c32b5d1b5392db62c6a2c9f829ed9f937ed64452fc5e5c108c09899910c", + "nonce": "77459442b645123943d74d7a", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "a70388a907779a2b59fd6f041541925127745559e2da6b2ab7ac9a49132bb027f1918a3ba93c7b01b0028cab840213f8d1c023c57665770db8ea535c8a58b6035f07acb658b3b8ae611e", + "nonce": "77459442b645123943d74d79", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "27c29724f99b22905daf2b846ceb4ded9b49a63e21328f361308380f7b089aecd18aae537421cb92d5fa64f7b962bf6b7ad9932769754462f6bc0f62ee21c692da7ef6097aac3f6b5b0c", + "nonce": "77459442b645123943d74d78", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "a62b76c581b7b760013333e13403503fe2676ffdc280505aa43bf224a7a4e35b86ddcf2d18fabc58aa41c25f2bd3ff0da69ee0064338a220237506d595084a8a700a4317d34947f145e1", + "nonce": "77459442b645123943d74d7f", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "f90d3dea091e3f1819192e5318095db84825b5b19c1784b457ceaa2316f77f05f1336092ddede4067656ef56b047819abf320dbbf82e85e25408ef1f1999b0e268d34a5d5f95bc1ecbcf", + "nonce": "77459442b645123943d74d7e", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "1e2509e24467019a1e4a74c642a6b96b95033560c66792a4fc82e52b6b3e17f7072aa0f9dd3492ab04d8ac8fe272f47db3f86e2d4a52f66578f8429ba19c27b318b5ec2efb0321770637", + "nonce": "77459442b645123943d74d7d", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "a6e026dd67835fa7be9c21babb9489ba9301abdfbfb3723de528bbe96ca50c76e7c4e9e7d2aaf4f1b6160033d0f678b2cbfedeee5ed173cf20072638d13a1a58bbd492a0e3c4173aa510", + "nonce": "77459442b645123943d74d7c", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "eba498069ef7ae26d0d822fa12fc469cc7a4f448ab6b4a20f4eb631f42bfeb9d0e1bde4c57089a168c44557d43f48cd49f1581505109426a76d37841c33562865411debe44052d5c0c4e", + "nonce": "77459442b645123943d74d73", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "c1742c2583409f1a219d733caa07b85009f27c0c195af19679c4f92793570c98cc60009e65d5b8c98e2a4d2bf6948c578e7788221b1f9d5a04b3b8b22df01366141db6beb14e2f134d24", + "nonce": "77459442b645123943d74d72", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "a28eac67f1c7d8e0a7d10da1c3e65c7e7e7b6e788fdcd33aa3eed6f6037631a0" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "3929d2c79d0993cce923b502ff03811dc8328360b0dece71485a7994603dd3be" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "999f41ef5b39e0faf7b2fee973b18e2018c8d4da259949d4bea9a595da070269" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "b07d6319b68e5aa7cdade4fb7766ede1bc7d13d1cfac3affd706b0a424e8a48c" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "4fb7887f90bd5292d4c98acd92792ad4c7a838f83387b8ad138f02c5af92a090" + } + ] + }, + { + "mode": 0, + "kem_id": 32, + "kdf_id": 18, + "aead_id": 3, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "b6cc2b55f03081c2776579d068bf739a3ba08b1b5f4024a08742ba9479c84ecc", + "ikmR": "bba0acb2bae9183843f4fcab325719df1f85185ac0a4321dc07c898df28770c5", + "skRm": "7151ed52cb01f89a870ff5f60e8c2be7eaeaf8180f5c741309cd5fa4d00979cc", + "pkRm": "debf05a05f1915dd70ced25ee4774bd54e62abcbf599d323bfe2d1f204c93668", + "enc": "aff395707b2a242b43affd25e43d58776ff83ca2fc4f41d084bc871bdd207e10", + "shared_secret": "25327be29b041cf5dc4c556ae05c9fb4c7edd4fc97ec49bb5e3d9ecdecf7f1cf", + "suite_id": "48504b45002000120003", + "key": "39333c6bb058ea731384f9f4c5ac4869cf1ca12f74c3a3797c201b85da8b338c", + "base_nonce": "75594df8aec04896f4c6defb", + "exporter_secret": "36c5769172c6dc522bb7f94b53f56e16915fd06bb46888b0be1e20d759f66b0c", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "aa62e6efd433c61b5a411c11ab9c83afde2ba13015ece24e3fe9208d9d6fbe8a665f778cc2fda3e13bd19c28005ae0255870df74227a2f854bc1601fe313526704c1572e60d9b4aab1ba", + "nonce": "75594df8aec04896f4c6defb", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "06d31929c3e0d0c8bbf51d672f8d232782b3f39f162533192b31b729d05dab061d4907f0be8488b9d40d989c767d016b866adc0e6e1c91436b1b00b1caac7e290abf8838d33bcd7228cf", + "nonce": "75594df8aec04896f4c6defa", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "dd31f435b5b61f23ac48c78773102d6c54ee8b519eea227110f6fa89e09cd10cc12d8eb1c6395b0961a3b904a7eea16e5578ae8dbcefe4699777da2a15bd597e3b991f05a5ec04e19ecf", + "nonce": "75594df8aec04896f4c6def9", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "1f1cf14041f5cc5c148b96ba0c3ab0d5994a2b657ac1c130a65b1bdb9b9ad3c976fda064ae22b719f3bfa067f0cee62e5a97ad97410ae73597b038a24a5344cde97e496df8cd6d7c63f0", + "nonce": "75594df8aec04896f4c6def8", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "c783498840844a6463440cb835d8ea2bb16202c4ef8dcf7dfc968d9976e3aa865d2240d9327baaa4f9bda0bf680853c49b51f4ddc23de917a072ded015a845497eae2755f0b9ad53ea63", + "nonce": "75594df8aec04896f4c6deff", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "b002a33133e8ca80ab3e7695b5e6a8d1ba70326f207ddc2a51fb9e23559db75f61bd2833f7fd0664a616eb020fb16a118b13e0cba16b04101415e70e1d7bebba1be94f4a127f40662f54", + "nonce": "75594df8aec04896f4c6defe", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "f3d99aedfecacdfb07e5ca5bd31c1c2658ca54981cd28e4f90fad6aa17a948ebc264fe11ea30a55d6796dfc8326dcee480686f5b211c2b7e73fda374cac55f99f663e0522f2fb45cdedc", + "nonce": "75594df8aec04896f4c6defd", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "4a1081671aabf5a2d936bdce3cfd9577763f7854b11974ed66d6e1573ede4aba5b1715b02ccc2a6f4ca277e2dba04a50f9a19aef9eb13ffdfc96885c3e8d20b351ac48d1e755053dd510", + "nonce": "75594df8aec04896f4c6defc", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "0b864ecd2c43eb789afe207972daa5763708af6893b830e2535dc87a8f0130ee351d40562fa2ee54e8670d6c0ba8ec94ce44239cbde418d0c2bb0dc48031452ae90bfeee29359deddda5", + "nonce": "75594df8aec04896f4c6def3", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "e5f88e8f8e71b99161a7cf1ed96aadbe6aa5753c6106de72075dbf3125ad5d87ececa73392950f167e23be7e48b1fea7188dd6ab67e2d295406dd0139408ff80c80adca17f327266f3db", + "nonce": "75594df8aec04896f4c6def2", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "d7f55336c6cad3b9167adf0216d32013395727dd377aaddf40b27be6bff48d66" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "a82424271723bece129851435bc5a6dabd52db0eb847174c76aa0881c3d8ed79" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "f03eac727ccf10fd07ea444db49caf243c46b06909add8ae0c71869a74379773" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "5f2c2608b990c8fce7b30badb76db9cdbba89b4f16b2e0f7ded3e6da700551ae" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "f48f363a63a8f05000e8e3b27faca5dc44c73aea1cd6c0e53a3c40f4bd01fc7f" + } + ] + }, + { + "mode": 0, + "kem_id": 33, + "kdf_id": 19, + "aead_id": 3, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "5ce944f454afe857bcfdaff8e81202fd6c1850ab2b7f81ac21703e034c87da2b2d9aba7da4a3e2d1a395c961dbe17633757f324274b15294", + "ikmR": "05010b60198748053414d63c47ba705c6440c9cbe653738977d08c6ca32d0b82e27ba4ab996412f59f05cb73ac7ca0397ae450aa8f12ad3d", + "skRm": "7491fbd4ccbb8c10743c87837d06c6bbdc0221dd527ca412503fe362360894f3a974d3bf98e55562ec592347b8279281069a744167477004", + "pkRm": "fade88e04535766e153ba0b057f6abb2dd800ddc7f9c81261c4c36daedfe24fdf7da2f5509ffb5747fe176df95d2685ebf31a92f9d35d95e", + "enc": "63f7f749b717b00162520b49a89e6ad570cd333b685468dab6ef2017a99b7e194fa3bf572cd1d44766f7c1f2a1d5a0642799b860533ade01", + "shared_secret": "f2dbcd87d122d22995c20d8ff6640f3cf15a3c1e64ccca0f2f0a81f89819290371ba5a5739dff9e3d95845a62f0e28e1d3ba3926789902eb1bcf687f08219416", + "suite_id": "48504b45002100130003", + "key": "50d9c0f76c495240501361876046a37de8439de9f70a48d44bb3df81506e48d8", + "base_nonce": "62c1fbc028752a16e82e8f60", + "exporter_secret": "3e4c62d4f0fa55c78539cc16ff663a54a1c79cb28ebb3411bd84b98968061cc7220eefc446bf60aa064a94d447f57ae18d7621b5bf68082939b8e725e15e5740", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "5ad3ab9053041b1c06ff23ff1e3d6a1bdffb7ec3c33b9c03b4bde479dc2d0f9e0950513fadfa51d4a03a2f81222136f9ceb7e8b3ac9d03b5be9a17e98eb0946179605c4a8c3bc8070380", + "nonce": "62c1fbc028752a16e82e8f60", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "83ef4e5703b8b3e384f18d5860faee2653a749306859b405ce54a61f0ead7189b20f717901cb7aa53908c6c99995f08874e984053f7b2d9cf8cbe43695bde55e147ba3f4e2d537011ff0", + "nonce": "62c1fbc028752a16e82e8f61", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "b023a5543c582674a30a2147973ee1100b8c479fd755802da42c466a7fae18a4bce3533e47ed6974366e4513c28278272d039144b0c1c1d53389d5766d42a67f28b378ab015de8940c2d", + "nonce": "62c1fbc028752a16e82e8f62", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "c54cba2dea9f6b11ffb2a1d19805d4d96c8f65564d6f01431037ba2359452e7795ec4f84713d74e3a0b11e065fe2cd7202853f04aa0932ea0b8731635f1af768761b47db5f61e3426d07", + "nonce": "62c1fbc028752a16e82e8f63", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "972268de3380f5ad101ee1d88b63bd47eb6459b176a43a5933d0ef3ec2d5cad641c1f7d46c0cd128d1b6d547b7cd719951e7f9973c0903ad03c2a7b8e9cd7fe18979188e0b01cdb80e59", + "nonce": "62c1fbc028752a16e82e8f64", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "def9ef424da0409ac61384a355905c6325d84396c39974034b9890eca650a4166212c396ff52e04b2dd8a11fcacd7b49fdc5a8d859b4e45152c50714bac213b09e6121610c3fbfdba107", + "nonce": "62c1fbc028752a16e82e8f65", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "70be5945d0016488a7ecb8edd0fc2e28c2e5c6551f6b80162c4fda5c5286839a36cd4e67fb715cb94279d1d5b488518d1d5e7697aa13b78dc677129862098c2d13cdf4f4f32010bfbe75", + "nonce": "62c1fbc028752a16e82e8f66", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "962ca42b0bc9a653fdd9ee4875c096905b7a083fb2a2532423c139af182ae4b2b6ab667fa1b079a8f72b4e321d3aa5314634aea5335fbea86a88234d102ebabec80c4d324d1168621800", + "nonce": "62c1fbc028752a16e82e8f67", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "91e1977b06039ba6f87da41f5a606048efcf9e9bc8b5f35960d4b986f8b90d6dfce5695f3bb8d5c36e8a65fd066aec7acd9b6707dabe53190d1b7823f8dfd6af22c78c1d9ce6bb812949", + "nonce": "62c1fbc028752a16e82e8f68", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "dd759d51420e6047aa826fd337133a7830fdea09e6db565af08d967965571889fa7a456b942994523a175415d34aa98205de6542311059f608f11acd4c2a99204e59b5aff79d04430d73", + "nonce": "62c1fbc028752a16e82e8f69", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "2da5afb4fb6e17c75ab26c04342c5258fa49b8560d6097df09e5e185fb6bafa4" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "082f74e76aa2aad139ec8d16a816b64157267b6151267c6af143972b63878cbf" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "bfe8ac225e0c315b3a896e66152b65900bbd1e6857ab1317f75b5afa84ab524b" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "1dce8c0532f6555af6c509ec4e10e5e084052fdb6409b613dd88d566c648728f" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "32e3b06e3d4ca33010b2ed022e04ad08cbc8d8b66f94630bf9701b0797cb6259" + } + ] + }, + { + "mode": 0, + "kem_id": 80, + "kdf_id": 16, + "aead_id": 2, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "67840d6911546d7784d58166c17a43c7a19c5866cc6db1b0c61f509ba0315927e87cee3ed5836432af0ff3721e87baacf57de68411ba96b8d53d0bc6bf44abc28d6daab4b0c2452f8a8e25ba5d28bc734191c29a686c65d852c941dac3764cb44b7cdeecab2398339831370b9c3b2354e52385f93b09a46a6cb7fe80d94edf12f8ba4906884c5e007212edc9a26837a1d6a5e8b18f41453cfd88d2ddbba6e1be", + "ikmR": "2a881bc073b87cca19d9ef6992ae689d4c58b047c274b40784ec291734d7697f", + "skRm": "66273a348484a1872c621b19cf037e6f8190c80ea79938d51d78cbd88be4403e", + "pkRm": "2383c65847b054307438b3bfe68397875609e43b1a57d46573dc4c2992c457e3648d2b5413636988a33922f7022505b29183c5862625a8d3bd820714cbab89f9c1197c52748d011de275a7522cca2b866669c9930c84bac3ebb0a59662391b437d312eb786662d19cb80444f352a75189aa693a2b13abab836c8b43eaba003d688c70c9721f044cbd43e9cf49a508709775878c0438728daa8fa671da91bc978fb3a2110110c317155211457651392a7cfb2b45b4ce21aa31b9903c389967170f586065073a8688abd37298c829998c05054f9484ebf356bd90b1212880a27f431277a15605bc85b6c97475b34e4b875bbc6b77799324520c0137510383ac02d48515ed01af7cc1265a4acd6c3baa3a50b0f325fbf0644e28811fef2822e01b5364906a7b99c152790c5099ff136989cfb6f04425622e15c107b329b49119c787077497386dcbec64502a0f537d135ab2542b3653cb377c173d173cbc7b398c19a7fdddb3ee5d12cdbba96379422f935131efc6831ca9e7f9a86fe298b2f29854e3b9b9913831dc66bf1808a8a95ac30474fe467406c7b4ab45912b4e29689a7ad9668ba6b36570b5c2fddd56fcce0c348998488813fc90c0232e70f9125a31aaca469fc3764e23acdbcbb1a106ddd941f0b923a76f3a7f9a91582340188eb221d1aa67de6ce503cb48cdc7762155faaabb96ec471d1236597c577bef207d4e6594319a0df1715d8c1988a234351a6b31c12334e59bc9d86050e7862f85b7bdf418f5fa276dec41adbb6c223ec5f12579d4c552779200fdf5b213476cc023c4db5b9c8ff8b0ea9f5291f3789dd1c55c340111e21284a34a445fb2a27e7bfb26128b04c0de3b73ad6a39606a0a84e347c62c26e11e60f6b22a2234baa0fe6233cd82f42f282d90370a1e07050fc8edb3aa895fb03f3f29533073e82dba544765c70b44ec70a91aed89f60c63499a635b565a571d7a5ece50a2e994d3d1736ad68c5bb4bc3ceb76c00ec38764b0e2d912d3a91386ec3ab7e3785611c8f2c310cd6d8044b3caadf959799bb1bf7523ca3f8ad95597c35e46412503b2ffa46e88c95ba944d5e598e23a4a2b3266553d5b92e856ba2b95925574493c72abc02bdc201155a92600069194146592ed89cab8601ba62074db75814cba0ef3c55db1079b4a4402d477ac6d46c8ce3224e9249ec76c9971bab8124a08e2c1c4bda43dc3aaff5a60b028c6ee0fba44f959051751712b09e8577207373c34f7cce084689c61a7f146cbcb697c400804f38d5b752a75f07451d737727c67acd67324f72173514620b0901829424cbe1a47525b69a9d56c579a884de54c6361253f134c503a09ecffca99e997d57388f9accae771820ff86b7fb52cd7dacb5e3527a0837841e5b58d2d1132bbc8c8d1359957c751ec7ca223b97ff214aad4a26cedaaf6fd6102bf68d65dc5d0db84befdc8929b01fa1d50d5087a2d19583340809e95861c5ecc6e76aa2b2f454c6283e6000cbc2209b86790316996fbb41ca96b0a84b877d10b84bdc7b79afb945c909c869652a4174725b437b1042b40637a96c04a20549626efba2dbe46d5f53c31bf4be525b990e3331d1662d8ab323d4555c253b79c3342bf81dd102c2d027661bda892300b317238d39b97eab4725270fe810eb3d89c13e045707ad11a164a1be68161c1c2a1b61498b85c35a755de03d6dbd567219d89337de6215e782776d25fc72561a739444e2829da8be092600506d008313752a3f32", + "enc": "3b451618d1a4a1dd3f52e6b2780cc3b35b61138143e8dfce4967be861c63f404e14756fcd7c4696259de3f2359b6855d09538c7f9456c2fc15ab1288a5d42c74c73ebf6ef0aabc33093f19cd78972fa0161188eb2a3bd0d888e04ee2a0fbab2cf2d48d515b7ca243a00d9d23f01f6febb87ec42dbdd2a8c9953051e7526e0b4ed42146ce4e8bc9ae2b6f50bbbc22b5e7bbc5aac9ba06b5724f87b35b03adc38144b202b2e899b9df940ebe7ca43a044ea68b99037908fd200468f8aa8572b63d63d81d71599b80843feedb8600aeb0049f78acc58befe361694cd3efdab34e97f97d104eb0e2d8a6358635b7b24130ed287aca8af4a4da143218fb4a25a0c0548003c57baa197f77693a143db14773654f943edad81c5e1c754cba3b6573e330586915df2c93e0f9cca31f9b9797e52b8c2f9330fa880006c5a1b3090b21561848ee1ce8a575cdf06e6bf018b9a877d968de5728eb8bb75c6364aa9a865a35f3adb5ffc65032c9ad301f2994db70ac5ba5003e43500bc73389b467ca3492c48bcf8cee538828331bcde810e8a28efdee045f96cd20ccbcd8ff0c0280b7c8e5726deb2b0bfa8c91dec20c52d47536ceb5fbe460d65da338a35e9c733447970196cadc3fb7021edb5e5202a6ddcc940e83cce3174a32415fae21ca78a8ad87aaa96a45fd0d0fa0d0b6c2a9fcea5939cc2634136f534985ca944ee2c7a8c14ff5502bca8829b721c7503bf50839899c35b0656c3d3965b98f27fef3c8efa1b19bc15e1d6dd512ab9f5e21a5efc76ea24559387eb225a16bc7714ba958f81c38f33c4a33e16a997d575d20aa9bac25cbf7ae0c269f5e3c53d0189c518ca16b283659c9d29936e84e1f6c823b9ce54114e1cdfd236b535a437b8e079cd8c628b0421e7eae8abae36087538f53d4bef9a02e3648e9aaaa443eaafd8a5fde69b113c15929af5f4643cc59f0b7d01d86003ee63e369eba12cf18eeefe64b215fa0bba8cb1196c29559aa2d897fd0f364ebee655b0e2591e0ecae521eab51a92e5adbbfa09868c0cff1c5db8e10c8d8b1dad6b443f7014317654608a34610ced2575b96a949cb7e5b52826f9b74240be5b43ce44a9f452eae036855e01c928efdc4d618039a38831b750e8244a718f69722b3cce3b93a7d9aae67fcbbe4ee63ddf8604dca8d3868ffe163ac242603d750e18390f8eee12567466296bc1655ce7a1d9a92bd872f49b7d0ea04002bb9ef1c7132e204041ba1479d4474823ec7bdfa371d7ef5c908e2a5b08c01c28c547cbf899f0f1094508e7f08cf5309d434c8a443ca255cef7f5fab7c2dfdc7bdbac947d6feb3b2bc3d4bbfc4bfc844e7e32a049cb0f0aa128eaabcb741b9c4fa9e3186a1b0e45962e6d796b98fda7c0ecf7bcaaefe02b5293c3415551790e93f2d4ad9a83b00e4adcc6819c4d41d9670e112e0e67181e326f18bf0e9718b8354e3d6062c76b30488b598cf7af2f4a96dcd0e2e2ac17084092a80312adaab4b1ebd5cc325c0ce5f96f1ae50717d3b1b6ce8b3f620679ac904fd97d47237d7b362698c647380a7e2ce91a3782d96930d893df4447039f895102e7b2305ea3506c9d68b90a26d4fba1dc03415d6cdf396ecefd7a94b27724c61", + "shared_secret": "9d2253e13a56e77df4cc2093d56c935ba16802a1068bd4c4f7915df4c8e931c3", + "suite_id": "48504b45005000100002", + "key": "ae09f7df51707cf2b42942cd88fefec4fa04d5bd186fd63c0f08b55d6d1762a8", + "base_nonce": "daff3625a20bd400bb9d37ff", + "exporter_secret": "8f039cbfc19ead64309add23ba03d352fe8dda8369614d6fd82ab4578fd94ec4", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "94b83d53eea6d673f998c65f4810c8401818bd5e9e86d24b77294ebed80e3c2a31451c6ce6b09bb316f04dcc6d24d099812917f5f9f57b70f0f5adb7577088e4db256f5bb78d887a4d14", + "nonce": "daff3625a20bd400bb9d37ff", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "ef1538d8819bc60387f2de35c375a8470846802bccdf17fb53114c26b01d4ea7eab73132f0d9a621d85644bc3030c2b963b0b11a804662de51873c65edb06f9e343862b70d2cc85121b3", + "nonce": "daff3625a20bd400bb9d37fe", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "d97f92a34f96c0fb9fb351f212994f7aa4f21e73601f1eea9d97ed46acbbe41fa6ab4be8b4e5f2a3f60870dcb2c3dd8b28ddc472fbb64697ffe9bc6d3774da3873cd51f1fd4189a91d63", + "nonce": "daff3625a20bd400bb9d37fd", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "73b16d5810b2bd59e9378335873469ccefeaf5d77d5afeb203577bf873667863190c6428de124a828b2bd7b19dca570c4db10e9643fccaee4250e590521d6542fd14b0c2a2e26dd9efc2", + "nonce": "daff3625a20bd400bb9d37fc", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "e2824e00775603373708772e204f5433666d639939d00b7efe4a6a7e0fc8506cfe9edcff343fbf48d54fbf95189f180ca1dd415c53606f2953044230ca84c7a2802777a4e2deae25049b", + "nonce": "daff3625a20bd400bb9d37fb", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "8282d0cd7652e7f39a20921a4711ee948facf1d4c4df5b6979f53952f70974ab6a133000e158d9b54b0111727c19bcbd2077e33dd39206892cb7468e9a8b9acf6875729088c9f3f7dc0f", + "nonce": "daff3625a20bd400bb9d37fa", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "d51b58f8e0a6b69837aa79d6c939baad5815c6b0911c3df3226e2500838d0f4ff5c52cedd11656df1c8697fe9ca6f5b0e309d3eaf1174125e312b1affd92c45c837fd57378802f9db28b", + "nonce": "daff3625a20bd400bb9d37f9", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "148bcfd077af5374cdba5365af4d0fd66edf1350cdae4ee462697fbd897535875ee09e7bfcfd61df12b9cb3a6557f3aecacae42b31477f9a3bd17b821db0ad78828f947a04182490c216", + "nonce": "daff3625a20bd400bb9d37f8", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "1ef0d51b32ab9c0e224efbe9ef1759751651044b50fbce517bee73ce72f19b37e8939aaf4d75f210810d564dbfd2c1bb0f6e6abb00cf63eb24752e10abdf4e5855491ff63fe44ee9bd87", + "nonce": "daff3625a20bd400bb9d37f7", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "55f7440e586d22c73c61cc9f9c8a723c9d6aef2b90f8feea359cb066eba9ad707225857d84963cbdd573d2087f64fe29defc91ad389168f06e1e6430cf58cd469d7928c85943def0f1d6", + "nonce": "daff3625a20bd400bb9d37f6", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "69273dbcb0c70786d49f4a568737f67724cacaa1f80e1bef50bb369b017dfd8c" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "ac1b843f385b6c2a7a6d26edb38032c5091706c5bc855570b9bfcafddb9bb1e1" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "82f85d1631d357890d0fe518b35ad845db771c031822d2a1d0fe6611441428ad" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "90678e326de30078e260ae3802b17321322e423895db1dff99a2bb89360cd673" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "9074725fcb99ba04bbf861f4b42be52babef266ab66278ae95862b1a86f0ec1d" + } + ] + }, + { + "mode": 0, + "kem_id": 25722, + "kdf_id": 17, + "aead_id": 3, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "ac9639cf4581ac270569ed0fd1f4ab0feb59db2956c91c38fd1744768102f69d29ad8d4e9fda6043676fa808d148fd448397fce724141dd9b8e9827d274a9fa5", + "ikmR": "cc6ca9078b703031842f60acb018a310030a04cd8046751c671ad247b79d35bb", + "skRm": "ade62d76461f5fb35b5de3419f10b4ab4cfd81512da8e8a094d51ad9746d9868", + "pkRm": "4b9531fb494a29413d0ab36a92c7749d5b409a137f1a05c1385a6818308bd3d32f1065b73cb9a2fe9b77e74b9bb71a4a1f0aa01568687cf413893951d5e983e39904d0f3ac2cf457297741cb2658123ac5b0116863356b4b55551c5bb56faa9c80a62417ea90dcd683601c5d2a977d0d74010f975c15622d01451d97234761c3c8ff185c37a11acfc0c5fb311b2660c7ec28438498475d2c2dea12ac0387ce3ec4669450302fca9a0ab93bd7cba2879c730bc17d6b5417c383c1f90b9807343322f85d89abb16686bdf82176b7b36284678f017990ba4a724e36013bb38e5cc762c6e8c4127536b44659e9890608d2bd355148d5e3024842011c800c5ff72e20da517aa669c1c78ec5a727129692ddec53d4d5aaa35016d63482d0a835977ca0bff350ebf7b4317259d25a03511abcc5d631c979cb1c18bb829c257dcb038c5085105aa88e5744e9507a247877796693f1631469f29dfab7a5d1ccb818c03ff652c4a896b9aa9264192b2f316973eca345c1f29ad6d9004e6a76afec630208ad6bc058eeb81a1380ce567b57ec24a1f4c725611591e85a8bd0d8a306e32fa7002fdb4c9ac9d02c73f557bb9c0a5f55295c33b9adbc4469a6a321eb39dbdb13fd44a556200e33184fa44346741c12fe097b22c778fa25681d954da3843a609a1ccf38478658883bfa6b314b6b93e11105884a59968a3cf89beff4717db7ac938b89683bc588c6553389b53ca744099741f1d96e84ab93aeabb10e4b5d1269244fa93b5ff27298872346733e38c48e92f817e1e98c61c220a1069a3705810646463de7076c207d5d40b7dd812f58d8acf171900d7b8d9fc56f1c2929dbecb76580554ba6b0b0447a8f6a6028f4cbb1933a58a26186cc4671c8caa6286d9124cb53b82e12d90beb792cec4c556dac44c49a37dd49c2d42a917578ac08a51699331c79babb43ec2a3647cc37db9316920693e194990baa7443a507104d3b62b015a199432aa606b4613f8cc63bd0565d66ce41d59b273bb6cdb95ebdd255029cbf6f6221aba898f2a8868239975dd00c9a520b394849df134b318cb4e2b25fad4b0b92d0c742bb3bd0c8c98ca2739048277d4b2056f3063895613d0a06a3d9a3bb86cf47d8158d10cc6537008a7469f3fa0609604d896918e4676598232b30d9ca520ab308610f92d7b4cfbc3dcbe14cf90c925000501737a5b0f61f98006ecc7c21e934c547e4800d54b2b578c9b4f435eac666094720fd45862a07a0121c0971b57b3fb69dc2c4b47dfbb47504295cd58adca7c0da5244dd7550576545b214c93796a9c0458b3d887947f8bfa88959e7f1c72821548eba7ea9f74a787c2b40b962ab58835a52524a94079a61284d7acb4943a54a117fc71b94776b732a644f66c451fbe34593a44d57fb21348799608959fad5c84e990951aca92c8bc68e084d2d596307f586be8cb3e043507be17e70312156eb6e464cc530377d23f32a48fb6921ab320a324fefa807c3cb262c460d9c1c8a23c9314cc3847fccc66c9519ba2756f9956cedb8b42a38cd25f7237cc7ba5d50c8c2ac446fb7a4a76762c5f151d04682122057e793851a3496d5177782c4afe2688a7ae2a198156d0cdcb4c64c8d282661abe19ffc2ca793ee9a2c190c5c6852d1d743384730c92c1bd82d4333642a14f7e3cdd98b1791c98c1a09d3a0dd71b1e253cfc077801a0c2729f53a3023", + "enc": "b0e05d539064754e11737ec32a268888b7fd7ccc11cdb3465860b26e75a5976b1e586c83503332c395cc312278d3bd6a8118db9718397dec4bf7a7f0ddc1d9edc0d0072b5bd8fe4861d6a01022cb3f30bac913753c60ce38fe3c322d60ad4bdc41682b292bae49226e1b01736877d232034170046b4058111d12285c47b0a3efae9e59654d3a7ee637d4b2fb00f17bd9337bf98cb59acf2ae53db40ca11910bfb639b82b15d9fd4be09df8d5e7b3acafa9cd24df808ea8557e86c325d49387ac8b2b9616d1f76efb6fd026345077641d7fde4ad3a83ff10f67de3eb4ec48f3045ed2032c3a9ec9642cb70bb7bc27d0b56f0a6b323506b8d25c412bfd25897f228122bafe2e5f8e55112c9f8e7c29d6d2498ace41742b7fd0e31a12afb2bf1beebcf63e387b0826e5a69594293dc2f241cf7dfa8cf27391680f3d72e8c90fded4605058168ce313a9de059d1a7e34f8016e62f9c824f440245498f463420b7736446b8fba0f8848b00094cae0749d2f2fc6506511c7a43774eef264fb3f8b20c46f50e394a325dd2de4b92aeab2db9d8f29e7e547766ebcf78000a1d33a74d0738f693e6d2389f6b6ec90a608b50f07608c417e10f6f7ba0f0e489faa6bb93b78a189ce8a02035857628c44f3edbdcb5b1a61c0864209b5bafb7ee9900605321505bcd6e1579f62ae97ada8c030ec7fb3591142348739b3aa3ceea934b0f48619011301a2997f070de0a064cff27beb55543ba9447e6ac0e94dd171ac471ed3f773c4e34e9442c91da655db39895a2c4f290e900b0c3b37691363a1ac5c78db70750ee0ef54f80ef631cfd920d78ee1686f67536cbf1a74fe19f90c20eef96b02e4e34030a0ea833179d4ae5a6c17c423271b4ad59f9045453a876561275d93d82a87af02c15a5513d8537d954fb42db00edaabf8853840f00bc618432c6a9cd94b990549a35bdab4b1be7a101862a3e7aa36f24513314751b2a6648f7552a1672decbca45717098c6808f12f341139ab75b5af14f895359b1152f638a3cbaacdb355ecd1af2daa5ae121d2dc68c13713dd99f738e6c6d9c7409365dab6027ac1a7a71e0e6d2075a1593cae0a664ab04cb0ab0711b15a5836e1eb40323fb60477215fd40f9b6b52ac7b2e73dde487d729dbc3c7f976adbb28edcca8948b1a22f11943d367452e817f20ed27d4feb5341e4078164bb0010643d91fad31aee0c446274cc511501ecd929f83e8489dc385cd1d2173a7e63791d5a7eb7d0115389e9a604a999a2a9b443655876187cb060ea8bf5272fd06b85a33545ffd7ec6e76e866f6f58c9f3214f16125bd541cf0dd22a40042e19abc47462f7bee257958d330a74f6abc17c3dc1f23fd7da0b274eab80dd6691c94ed5694cfbbce7d25e3a37b94358b87b57777ebf82d9a852301e3353bf6356f26eb3d293ac97477b34734d7c1efaebfd2c22d7820ecff59b7da55ccd0f2a54e645064612b716736543948bfb20234ef9d5a61e0697d8abc940711632f56a14177de163c7d0b1788a0cb17272175ab893370766d75d28680cc2593902aafcc6ce2f25e3b71a9c1ff5f0871a0c", + "shared_secret": "c3b302f7ad7e13ab4713facdd0d8058507133e966519acca3af01ab2d5c96549", + "suite_id": "48504b45647a00110003", + "key": "946a26bbe80fcb3b39e15971fd7f4b41f05826073fcc015c69a5c4a430166920", + "base_nonce": "df9df51376022d86761ae77c", + "exporter_secret": "3e5b5eaca99e2a157c08013d7ee6c5ef40c09f9ce2855319dd8b01173bfbfbfc80266664365ec9d6a7ec10c7804a41d3f482ac7d530018e3e41bd2b5fde27087", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "b6fc2a492f5c22a6ce272b8cec7bb643f3ce657fbd58eff2d689666bd7c67fd1fc9d8626b56be90dba0277a794220f8cbce54dfcfa07572c7c88266a9608ea257a8fbbebacd960d7804e", + "nonce": "df9df51376022d86761ae77c", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "b63342e59afd93e0de52d81474f3a6d58f828e065235608172c6e766debfe0b943d3549dd920b55cab0cb6b3e4fe9d4dd543636255f7434463cfeca6a55cb1b336db1a4ff89659bcc62a", + "nonce": "df9df51376022d86761ae77d", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "4d8804e072fefaade1b3a50a6bc7f68168c914044cc42a59b71599e5d3d11314bde545bef1094f97bf35ff95753d8ebddebaf3a372bdfe1604dc00b7905263683c81c3adab0b181271b3", + "nonce": "df9df51376022d86761ae77e", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "62c8227de4708510ea92e1a17cd83e1d152b9554d12cd2a66ad0114c1ac5fa671b638a7093fd5580204460bc244b68d059781ea685a09c820646616b537ff858bc88a0abc249deca31e2", + "nonce": "df9df51376022d86761ae77f", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "62becabae52c623b55a3d2c36b3095ec9f2c8288a4bc1c65501bb2b538c0cf9cb2381ba491d84f98114e77d2896049be153fcb22b4d1f1630f78b40e9c7aeb8f082e4e28564ed22da5ca", + "nonce": "df9df51376022d86761ae778", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "5680b5a63112f7e5d2ef8ccb6ba565789d84aad2098a30c0efcebb415ebe81e34b8ef8d7eaf98867c4f8b15a5e7b3d9a3f6e4d5ce58d145a15e434aa1dec0fbcf4a359de7c94f0d4e6f9", + "nonce": "df9df51376022d86761ae779", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "086b84a7d0e4a326412bd105682607ca4964d886d064b74480416e9ab85aca603ae4c99188a27eda316dbb9ed21f717748fa83b362f92c3d2e05550d992f9da721413ed5f63602cb833f", + "nonce": "df9df51376022d86761ae77a", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "02b96decddff80c769b615b481c01896a81faaa0d794d494192d93aa84dea4121baabbc2ecae188e15e02ce61dc7b7bed8223d91f7246284d7159d7580718cb5ccb654d1de5209c71351", + "nonce": "df9df51376022d86761ae77b", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "cf1123ee5cc11a0ec0cde956908e607978ffc7009088f5e2bfe282bd9a11f6866ab7712e83cea23dc7645cd2000d4a7a8e006382f9bd26339f77e7c52e824ac79ccd809e57d605b54de2", + "nonce": "df9df51376022d86761ae774", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "29c85e9faf72c45f9bfae777917fcb92e51e1c4dd416e97521680396e359269058e9a0f48d2cf2f7a924674e0c3afed4716badd6c9506d3f8bd52eae56b15580edfc4bf814228ec5ad89", + "nonce": "df9df51376022d86761ae775", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "941099dcecdd679acb7ff4e090cbd9b6626c9a510d93779cb812249e4d9060ec" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "3e66c753c7772c1cd27cbb1c84ff40e00d32bb0895a9b5dd78e59e8a83489478" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "7e3435f9d912542a08938b8d3e22f6caa1d3c0ffa892ace8238d4b816f39ebee" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "a648999e752e0c633c3bc1efdcd3ce1bf39f62f8232cd1d669bb1b96dfe1652c" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "3cb5f55ddf3b0eeb568853b7b90b3fa39c6eff86f3d7017466df45e900e421fe" + } + ] + }, + { + "mode": 0, + "kem_id": 66, + "kdf_id": 19, + "aead_id": 1, + "info": "34663634363532303666366532303631323034373732363536333639363136653230353537323665", + "ikmE": "8f46a5952c3a4e8389332362367031eae8e9f40036875ac1bed7e41309d797e7", + "ikmR": "5248d849e9eb63eba3e3e314761a160dc082063ae9874437ae1bd269d70301bc897f174128f521cad81ed51c8353c4eae8c5f00e477a165096d833d723312a9a", + "skRm": "72340a3721852dc4f996201382edbdf8b9cbd61c6f9c7874507b417d3467b1c0ad997ac9e874f3c03a30a0eb1c3f1bdcda9d5d6a812612b761805b9d68f7f35c", + "pkRm": "5ffa624d14cc6ebb1edb1c255854cf46a188d1e19d09f0a4797488e7da0df121033dc05ae10126d899466ecc4c2ca176fe73c3c4cb9487d74cab8c6830838de0e5c2b85119f1b9157d48cd246aa4ce31c28a230e2173a45985a16b5b0705d541b4721021460a7b6249378c18b2788e574a287978c879ac3e49e015ac844dc946218bd4cf7cb75f82063bfdcccb1fca72f5458c40f31056117e75139b8e235df7fb3557e580ca6b710e9972768844fb1b041b14557af1425b1c338eb14b4a1cb74b022fdc93b494d6b4b71b2cf4462aad19a1476188ee684ffa5c2d8d3c65c10b80ec21563ccc5c1f5862c1c3afecb4aad57b56170ba93e8c15bce06c0ef194e560c4da0b5b801420040a396ccb4702a6b61237b24c791db1a8461e24080111695332bc50c004d439b77ee067ad2299aa268af5f034462567a6b0a1b4376093eb897fb44b43c59131d2416c96b889941b9ba6933bda7153f282bb0c8cb0635eb2730f1b4b94b24a0104f979c3599eb9d0bae95387a4f55dc766858c438a4a37087b4544fd80ce85d6c8772368eaf91d41353ed2fb8f3822a3d1d14729e33492bcb2f6cab75e7723862b2c90ac813a0c78abccaae713270b54419f33211fc51d46caa445533af7a4bc731b0998ac0df098074fe5257c653540e984797a2422434f471759c8e9356327a5d1f83760fa5baf260a11a0348723c65d34219163296df1b345d183d4ec3234a08af27540f907c0e99ace9353a1e9f35107f72e104a137e82c4a721848b85031bbc0dcb81718b991455a9554d9a1d58e720778a374fe442e7217a6389ab83eb1b49f13344a07b03ab8e32361bd8a54a6fb163f40588a17178e2ea80e6d0798e0800f7e46a74417be2f61437b168fb514612ac1676b859ecd652e4741e1a43066c4527a0818f98e8026ba562bad3912b55c03849812461a533b450d927b96d7ba06c50831f507431472a9365507880341e71784aa99990ac0bc2c7b1cea54a8d03b2dc3a22fafa7cf5a377c2834ede9889d1758b37c0cc930b559518a342b76b0ba820b4879e5f420139c725a1f109e909832300850602c1c97cc7b7731d2033b3adb0a471a67359b62ff06504b6c44e10e219b03ca02d0cab0fcbc016407a643aa650e3b8d929b65c662c732139f7bb764cc41f314cc06626176244adc8047cf80798b4a44288e6c376aba4e648a7cff12a1104bf13897e3d73194797466ed044bdb764f6b20ee366a316f1314b73a72a23c178c752c21433df5204789054a005ab5db1560588b838d7bcb5b645a101cd5034a35b5ba1a506394f31c292cca739e308eecb2ce4367d2939aae389b109f62f342970e6f3757c5a3877a03766893ca9a942b3477b91666b9025b443187cd10a9b0b204bbb7762649c2a14abca1ac3be3bdc99dbaa639157b2f1e4ab25ccab594853e211556002bd97f21218dca3b2ea2965e82fcc23559f8c1b4b645a38d1487322007f309a660ab505c7ad80a62ed92b3ecbd6185b883c7c866bcf62bca53b520165c04572109886be875446b6579b8e2586fd314a4a489c9ab005d1e5c80ef59f86f5859524be64911c7fe1607042b6829a63e8b239b505ca27d28bba602e28a39b3a543cd0c25c103a9dc08224f7d8ca9d115a22d29ccf086b964bad24e477298c926a3c9ea29837bf519e55221cad98cfb532a42d331445b5a5bd58753d13409f6a8fc107405a10698d3973f7f07eb649751466b52f382128146d4137c52177109e01a7bce188eab815a9fc515021bc5432a577e44d21f56148f7b242f956c6568725048032e59efcaba616a89d4375483c416a1e03653e6a4fde1497ebb51ed9c2b864e23babd27e6d34cb6fa618c750ac7999897dd0b963ac3294e967b92081054ba076d40e7a6673bd3a72f3d9b6e9930d2d03062019095b8c7ddce377f9b961c1749833c95ae29723c4e7b218bb52fc2cab6f462f81f4769c9c8c32280ab92275c29780a17aa2d373c7d5495591145f5456024b4bc2816148f0f326a9ea38ebb10cf43987760c8ff8693e84c079f9754d57050a46b9268bdc2424a83f270459fe248784903b652732417190fdc90ceb96a86f5734e23a6fbb590e445b0acf47cc7aea2f976a0395d5c63da03593548484a229b3da00cfaa453f579dd6da55d14a93f6f24ccb37dc4957d7d826b733ace150b2c9bfebabc07a531178e12e8481f2de", + "enc": "b7632dab87cf3b7178783b3ec0309525d6eda88f19267d212b001674c630902dcfd7b91f2625b50140fd7897d690cc4fec735714f2dec501100b1b466b231c368b623aebb1ecc96f2545a247c550b333ad0ac996dc09d081b394165867885374f08dfc3d86cc7a904e6c023ca50b2e3f79c22be09f60f17420cb40bc5a2abe49b00355006940ce0295e66e0822d063312bb2f72864982f1b36a8c3465e7039ee510b3831c8e015f397f5c3a7bd4216ce7172775d5e2603f9c17fbfda1c1d707a06d3299ecb7df089d384e1eb8d861583ca65a290910be1da9f25a62f57aa56d7d84475868e28f73e40b36f24f5a08141ae5d3fba07e9ad9162065105bb52a17c0a1840e57e24e18083c5103db212fde229613afcbabe6b8902ec9d7e11057f8b30e67f075d6a7398d9de932ebaff25a414ffe5be40359ea10fbd3e205ea8626eacb86694e7ffd9a67db72390070e7765d181911f93dfe44291928ae4bb6aa0a321618282c916926f55880b9c2973771eae789fbf0bf1530eae47209b4ce21c6da05545296d9bbad9d0c52a4e1bea9a642e142c80f80c8dcc75bae5a91eb68571980ce4e4fc1945196a3c97c7751fe7332dc6d80a725006f22d18dee894fa0a048a37ce4f36ffac11cafa45affa0fede3c96f4a965b1e64d4021273a3ef1ae2756b73aa860ef426705b5fc8db9849530ec6c2a8d7b59b0aaa6e3c488c3e38b2ac66c2d219e67a69e8b05cbe6088b3e55a7254d6edb1393740b6d3eeeff7890aa134bdf9dfdd26c37466205c64bb4b0d33002e6a0516d7e4c66da312bb3787a3a4a014cbcc893218adb20816653419774c79fd96b8d9f9ec5fc4228b667dfd422abbfe63eab397a795a792d5282576f2fb8fdad845fd76b1a4c36ac561667d7137097cb05f1dfa263b0fb457582b37751d0e99ba5974da35470648f27c8e292040ce7baae171cf408b4dbea49119e4a5231fd7dd05a7de7fb9f0eb299df1d902d6390db5286050d4f3679970cef6aee3d9f146f4fbf383190c4bc1aef8f9c926db1ece0d3dca1ab7613879cfc7cb2f57b917bcebca4ebf699d6921f1f5c5fa3975f168e7cdb259b55a264dea83f23562b78972552cb5b0364aa5527efd1483a337940667f1909b87dfc8e06903165f38ac4d89a35ec5a4ba2180aabc127ecb60fed90d5c4ef9c5d5d8091da56cd455521cf7b208b4b44ca1182b3d45d9905b1183efafeba676dfd1c5f1a0f94cc35a94db90b9328d4351867b8c20af9acb8bed9e51240b43c777c524fb21c62629a97cae48fed9aeb8d6d00ba0dd61880b0cc28b640de6ef28573f99a5ac37c756f437fcf4ddcd960e61830b38c90474dbd69017c59c11488adc82fa811a9086a747ff6f1cc2178cd6f2878cae026f165287551b3aaf684c68f65679e62b51f40ed5b9c9e1d585ebf5b257a5852bc0370f3c124ade2184b1366bd5d0e1cb5c66136b769281eb00088d5fd3e330d13a277db40e47664567934f8e03a11c3feaf0654294dd4e6c2da9fe60cde90cb6fe8fdd3ead3ea468d2ec29eca921376fbc38dd2c4c9a1799281f7461b21426b9300210883d6f4f061bbcd9277b3a2e502b9e8a42e2b22705a17eeafccab6d4e16eb251299eb7209842a07ac72689340d607c20dc3abd6bd7abe27e6e5b80a30b8ea00bd11f2b1d92c4f2641f7307e31d53dbebae3bb2b2696c7f3c7198fd879ef3b6ab44f03893dbf97ea857476f5c4578362dda11c54e176055d2e1888d185533a9d672a42168e212f51647bde716235a1194d91d347cd5549c02a555c791fab6242f7270f70c1a28ac5f617742b865b26ed408c3f5b188f572688cbf4f3bffb4de6977c32faff6c741d5608c3e4b9aebc0817580a894dee345ca3303327e0cb5120198d99cb0cee0b00a36f1bfbd076998e70568d8f20e05ce7a42bddb0516318f22ff61f6f405bdb72b14734830d830ce389bdd2f89ca697b9fc198e1e6f4cce9218d0c62679a215e8c5e4b51e9596071254bf3cf7334b4622fca088d71038e80a6c94b07ef4317e355c6708b5f4f0a6eaadf6075f70e4666a22e8992f174131f3d61c3683e2ec0218488b6802aae4b1059dfbefca116cbdda39a7574143d17b1b8b201e8b8417cdd931872b00871f3d7554b52f6d6745d62e61169748e598b2f05b0baabb37a801a4b97602d6119405ff56d0b56771ef413131d1ca033a94d745ddfe1d0", + "shared_secret": "7c27e161899a7fa30d85fb53381365248bbd13995c2b02320f37c8c0b595be5d", + "suite_id": "48504b45004200130001", + "key": "5ccdff49884c1c2872c37463874389b1", + "base_nonce": "ac12fdc34e864fae75aff5b6", + "exporter_secret": "df8f4ebcd09b7f3769ec59b5da167053cb96c17113b764137f9e622e589caa5d80ab7a64fd96f60ad627c4a5b2a7f06052b54c2cf329f0e07c5500f6a7fb4cc6", + "encryptions": [ + { + "aad": "436f756e742d30", + "ct": "2e250e8f82eaa9d19df1c96db0a99f778383299b7acf8a886d6aa9524758afb664685d234abab672d2625f2d50622b11c072cc9beafc89774c0fc8325e47c86192d60c4553737f1cec38", + "nonce": "ac12fdc34e864fae75aff5b6", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d31", + "ct": "7adc01f503bfb4307ba40468146df4d62c45d1ef6ecb112b6b6079ca291a15b418958004601d54d6a86d97de92f2c4d31b45dca1e4d9a4695694cdd12593426634be531c858355244d89", + "nonce": "ac12fdc34e864fae75aff5b7", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d32", + "ct": "2a9bc49c387e2d290c8e6cc28d98266d7e92771f2c2c5d3dc2d90b6e0e6317afcf6967db70b2dcab4d00f6f8166eafe3792ab9da22a13ab5f24e2ad79f33bbae6649b1a0c6d23f76e7ff", + "nonce": "ac12fdc34e864fae75aff5b4", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d33", + "ct": "bc88b7e4a9d770021f6f65e6e8ab6c0de7a1e9637fe4516a763a905514facf5060de298628b6bafdfe1937f48df75cd002334a8fe97558defc2d0b6861141c0862983ec2e9b714a671d4", + "nonce": "ac12fdc34e864fae75aff5b5", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d34", + "ct": "5f111a73c54102238968994a4ceb3b4bec52833d08a7150f553808b17c13f3e31fc274f0dfa2872a0689057f2ddee0d4a7071411302969e0f922ebd00fa7df83e4f10bd3a7595690460c", + "nonce": "ac12fdc34e864fae75aff5b2", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d35", + "ct": "b438380fab3c3d79f28ec383a5f5f67902d857e72ff7dac003b8ea744ac564bb5cdd5ba33038739c3b916f6db017d4f90181aad9b032f35f9ed0ae299a105c96bdf0ba6026d83e0d6c3e", + "nonce": "ac12fdc34e864fae75aff5b3", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d36", + "ct": "8aab06e40480535907a37559013d6872dc6e15575f3bda4676a24c63d2f5a61f20e2544d04f17ad175260a93fccfe6bc937d14fc17c7077ce37472b7a3dc2d1ea667c348b5c46b43c054", + "nonce": "ac12fdc34e864fae75aff5b0", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d37", + "ct": "f5a7c6b267c52065175e5d2cb197df64a9255c0f06f5e289f358e33823c60863048a8b8b75d5eeda696b5698360e9551ecce8a4b861cc77694a1131c58d57650a15ec07a05a61606f031", + "nonce": "ac12fdc34e864fae75aff5b1", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d38", + "ct": "86d31161aa3c2d4a3ab9c28185677106dfee590ef8ad0852ee6b97b285dbddc23aa5f4e17956cd515c54bde2d368daedfd4166d5e975fc308b45c6cb96b3b20629dd29d84f36a2765851", + "nonce": "ac12fdc34e864fae75aff5be", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + }, + { + "aad": "436f756e742d39", + "ct": "34f5d546f195076eb824a5de401d3f7c43ce9a1f6131809645c6e4c9d9ca2b9c8878826c221538daa1c0192a250890693fc3a85559df877b6abc2dd5b902457ab1fbffbea97704d6362b", + "nonce": "ac12fdc34e864fae75aff5bf", + "pt": "34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739" + } + ], + "exports": [ + { + "exporter_context": "70736575646f72616e646f6d30", + "L": 32, + "exported_value": "ccb2ae445e410cdae9b4c78cb0d624c02feb2947d62d46c2c2f7e6d0025d72e6" + }, + { + "exporter_context": "70736575646f72616e646f6d31", + "L": 32, + "exported_value": "957e029b2e7f98ff23d8f12810c3450cacfc5ebd610f6dcbbe42199d07690e2f" + }, + { + "exporter_context": "70736575646f72616e646f6d32", + "L": 32, + "exported_value": "5829b84f811d4f1725b8153d5d01d91dbbb949480f81193d84c8e11df004fcab" + }, + { + "exporter_context": "70736575646f72616e646f6d33", + "L": 32, + "exported_value": "8d5cd4c3a429e214492f59d05119e9c627262502ff577ff0b6f14835f6e30c05" + }, + { + "exporter_context": "70736575646f72616e646f6d34", + "L": 32, + "exported_value": "4cc7aa171db57041a5e22b4f8ca46c154f0e305d339758cf59e110b128edca52" + } + ] + } +] diff --git a/crates/protocols/hpke/traits/CHANGELOG.md b/crates/protocols/hpke/traits/CHANGELOG.md index 1cccf127c..c3f976962 100644 --- a/crates/protocols/hpke/traits/CHANGELOG.md +++ b/crates/protocols/hpke/traits/CHANGELOG.md @@ -10,6 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [#154](https://github.com/celabshq/hpke-rs/pull/154): `HpkeCrypto::HpkePrng` now has `rand` v0.10 bound `CryptoRng`, instead of `rand` v0.9 bound `RngCore + CryptoRng` +- [#1539](https://github.com/celabshq/libcrux/pull/1539): Add the `draft-ietf-hpke-pq` + algorithm identifiers. New KEMs `KemAlgorithm::MlKem512 = 0x0040`, + `MlKem768P256 = 0x0050`, and `MlKem1024P384 = 0x0051`; new single-stage KDFs + `KdfAlgorithm::Shake128 = 0x0010`, `Shake256 = 0x0011`, `TurboShake128 = 0x0012`, + and `TurboShake256 = 0x0013`. Repoint the ML-KEM / hybrid KEM doc references to + `draft-ietf-hpke-pq` (authoritative) and `draft-irtf-cfrg-concrete-hybrid-kems`. + Split KDF handling into single-stage (`SingleStageKdfAlgorithm`) and two-stage + (`TwoStageKdfAlgorithm`) families: `HpkeCrypto::kdf_extract`/`kdf_expand` now take a + `TwoStageKdfAlgorithm`, and a new `kdf_derive` method covers the single-stage KDFs. +- [#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 diff --git a/crates/protocols/hpke/traits/src/lib.rs b/crates/protocols/hpke/traits/src/lib.rs index 3089fae70..9a64d82c0 100644 --- a/crates/protocols/hpke/traits/src/lib.rs +++ b/crates/protocols/hpke/traits/src/lib.rs @@ -47,20 +47,36 @@ pub trait HpkeCrypto: core::fmt::Debug + Send + Sync { types::KdfAlgorithm::HkdfSha256 => 32, types::KdfAlgorithm::HkdfSha384 => 48, types::KdfAlgorithm::HkdfSha512 => 64, + // `Nh` for the SHAKE KDFs per draft-ietf-hpke-pq Table 1. + types::KdfAlgorithm::Shake128 => 32, + types::KdfAlgorithm::Shake256 => 64, + types::KdfAlgorithm::TurboShake128 => 32, + types::KdfAlgorithm::TurboShake256 => 64, } } - /// KDF Extract - fn kdf_extract(alg: types::KdfAlgorithm, salt: &[u8], ikm: &[u8]) -> Result, Error>; + /// KDF Extract (two-stage KDFs only). + fn kdf_extract( + alg: types::TwoStageKdfAlgorithm, + salt: &[u8], + ikm: &[u8], + ) -> Result, Error>; - /// KDF Expand + /// KDF Expand (two-stage KDFs only). fn kdf_expand( - alg: types::KdfAlgorithm, + alg: types::TwoStageKdfAlgorithm, prk: &[u8], info: &[u8], output_size: usize, ) -> Result, Error>; + /// KDF Derive (single-stage KDFs only): derive `l` bytes from `ikm`. + fn kdf_derive( + alg: types::SingleStageKdfAlgorithm, + ikm: &[u8], + l: usize, + ) -> Result, Error>; + /// Diffie-Hellman fn dh(alg: KemAlgorithm, pk: &[u8], sk: &[u8]) -> Result, Error>; diff --git a/crates/protocols/hpke/traits/src/types.rs b/crates/protocols/hpke/traits/src/types.rs index a2d64ac06..fc58b9998 100644 --- a/crates/protocols/hpke/traits/src/types.rs +++ b/crates/protocols/hpke/traits/src/types.rs @@ -42,18 +42,48 @@ pub enum KemAlgorithm { /// X-WING /// + /// This is the X-Wing construction (ML-KEM-768 + X25519). The authoritative + /// reference, `draft-ietf-hpke-pq`, registers this code point under the name + /// `MLKEM768-X25519` (see its §8.2). + /// + /// /// XWingDraft06 = 0x647a, + /// ML-KEM-512 + /// + /// + MlKem512 = 0x0040, + /// ML-KEM-768 /// - /// + /// MlKem768 = 0x0041, /// ML-KEM-1024 /// - /// + /// MlKem1024 = 0x0042, + + /// ML-KEM-768 + P-256 hybrid KEM + /// + /// Defined by `draft-ietf-hpke-pq` (authoritative for the HPKE integration + /// and code point) on top of the `MLKEM768-P256` instance of + /// `draft-irtf-cfrg-concrete-hybrid-kems`. + /// + /// + /// + MlKem768P256 = 0x0050, + + /// ML-KEM-1024 + P-384 hybrid KEM + /// + /// Defined by `draft-ietf-hpke-pq` (authoritative for the HPKE integration + /// and code point) on top of the `MLKEM1024-P384` instance of + /// `draft-irtf-cfrg-concrete-hybrid-kems`. + /// + /// + /// + MlKem1024P384 = 0x0051, } impl Zeroize for KemAlgorithm { @@ -81,8 +111,11 @@ impl core::convert::TryFrom for KemAlgorithm { #[allow(deprecated)] 0x004D => Ok(KemAlgorithm::XWingDraft06Obsolete), 0x647a => Ok(KemAlgorithm::XWingDraft06), + 0x0040 => Ok(KemAlgorithm::MlKem512), 0x0041 => Ok(KemAlgorithm::MlKem768), 0x0042 => Ok(KemAlgorithm::MlKem1024), + 0x0050 => Ok(KemAlgorithm::MlKem768P256), + 0x0051 => Ok(KemAlgorithm::MlKem1024P384), _ => Err(Self::Error::UnknownKemAlgorithm), } } @@ -100,7 +133,10 @@ impl KemAlgorithm { KemAlgorithm::DhKem448 => 56, #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete => 32, - KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => 64, + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => 64, + // Hybrid KEMs derive a key pair from a 32-byte seed + // (`SHAKE256.LabeledDerive(ikm, "DeriveKeyPair", "", 32)`). + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => 32, } } @@ -115,7 +151,9 @@ impl KemAlgorithm { KemAlgorithm::DhKem448 => 64, #[allow(deprecated)] KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete => 32, - KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => 32, + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => 32, + // SHA3-256 combiner output + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => 32, } } } @@ -211,9 +249,8 @@ impl AeadAlgorithm { #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(u16)] /// KDF types -/// Value are taken from the HPKE RFC (not published yet) -/// TODO: update when HPKE has been published and values have been registered with -/// IANA. +/// +/// Note that Shake types are not standardized yet and may change in future. pub enum KdfAlgorithm { /// HKDF SHA 256 HkdfSha256 = 0x0001, @@ -223,6 +260,100 @@ pub enum KdfAlgorithm { /// HKDF SHA 512 HkdfSha512 = 0x0003, + + /// SHAKE128 single-stage KDF + /// + /// Used by the post-quantum HPKE ciphersuites. + /// + Shake128 = 0x0010, + + /// SHAKE256 single-stage KDF + /// + /// Used by the post-quantum HPKE ciphersuites. + /// + Shake256 = 0x0011, + + /// SHAKE128 single-stage KDF + /// Not supported by any official provider yet. + /// + /// Used by the post-quantum HPKE ciphersuites. + /// + TurboShake128 = 0x0012, + + /// SHAKE256 single-stage KDF + /// Not supported by any official provider yet. + /// + /// Used by the post-quantum HPKE ciphersuites. + /// + TurboShake256 = 0x0013, +} + +/// A single-stage (XOF) KDF, per `draft-ietf-hpke-pq`. +/// +/// Single-stage KDFs offer a single `Derive(ikm, L)` operation (via +/// [`HpkeCrypto::kdf_derive`](crate::HpkeCrypto::kdf_derive)) and a different +/// key-schedule shape than the two-stage HKDF KDFs. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum SingleStageKdfAlgorithm { + /// SHAKE128 single-stage KDF (`KdfAlgorithm::Shake128`). + Shake128, + + /// SHAKE256 single-stage KDF (`KdfAlgorithm::Shake256`). + Shake256, + + /// TurboSHAKE128 single-stage KDF (`KdfAlgorithm::TurboShake128`). + TurboShake128, + + /// TurboSHAKE256 single-stage KDF (`KdfAlgorithm::TurboShake256`). + TurboShake256, +} + +/// A two-stage (extract-then-expand) HKDF-based KDF. +/// +/// Two-stage KDFs offer separate `Extract` and `Expand` operations (via +/// [`HpkeCrypto::kdf_extract`](crate::HpkeCrypto::kdf_extract) / +/// [`HpkeCrypto::kdf_expand`](crate::HpkeCrypto::kdf_expand)). +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum TwoStageKdfAlgorithm { + /// HKDF SHA-256 (`KdfAlgorithm::HkdfSha256`). + HkdfSha256, + + /// HKDF SHA-384 (`KdfAlgorithm::HkdfSha384`). + HkdfSha384, + + /// HKDF SHA-512 (`KdfAlgorithm::HkdfSha512`). + HkdfSha512, +} + +/// The two-stage view of a [`KdfAlgorithm`]. Errors on the single-stage +/// (SHAKE) and TurboSHAKE identifiers. +impl core::convert::TryFrom for TwoStageKdfAlgorithm { + type Error = error::Error; + fn try_from(alg: KdfAlgorithm) -> Result { + match alg { + KdfAlgorithm::HkdfSha256 => Ok(Self::HkdfSha256), + KdfAlgorithm::HkdfSha384 => Ok(Self::HkdfSha384), + KdfAlgorithm::HkdfSha512 => Ok(Self::HkdfSha512), + _ => Err(error::Error::UnknownKdfAlgorithm), + } + } +} + +/// The single-stage view of a [`KdfAlgorithm`]. Errors on the two-stage (HKDF) +/// identifiers and on the TurboSHAKE variants, which no provider derives yet. +impl core::convert::TryFrom for SingleStageKdfAlgorithm { + type Error = error::Error; + fn try_from(alg: KdfAlgorithm) -> Result { + match alg { + KdfAlgorithm::Shake128 => Ok(Self::Shake128), + KdfAlgorithm::Shake256 => Ok(Self::Shake256), + KdfAlgorithm::TurboShake128 => Ok(Self::TurboShake128), + KdfAlgorithm::TurboShake256 => Ok(Self::TurboShake256), + _ => Err(error::Error::UnknownKdfAlgorithm), + } + } } impl Zeroize for KdfAlgorithm { @@ -244,6 +375,8 @@ impl core::convert::TryFrom for KdfAlgorithm { 0x0001 => Ok(KdfAlgorithm::HkdfSha256), 0x0002 => Ok(KdfAlgorithm::HkdfSha384), 0x0003 => Ok(KdfAlgorithm::HkdfSha512), + 0x0010 => Ok(KdfAlgorithm::Shake128), + 0x0011 => Ok(KdfAlgorithm::Shake256), _ => Err(Self::Error::UnknownKdfAlgorithm), } } @@ -262,7 +395,13 @@ impl From for KdfAlgorithm { KemAlgorithm::XWingDraft06 | KemAlgorithm::XWingDraft06Obsolete => { KdfAlgorithm::HkdfSha512 } - KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => KdfAlgorithm::HkdfSha256, + KemAlgorithm::MlKem512 | KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => { + KdfAlgorithm::HkdfSha256 + } + // Post-quantum hybrid KEMs default to the SHAKE256 KDF, per + // draft-ietf-hpke-pq. Note that callers construct HPKE with an + // explicit `kdf_id`, so this mapping is only a default. + KemAlgorithm::MlKem768P256 | KemAlgorithm::MlKem1024P384 => KdfAlgorithm::Shake256, } } }