Compare commits

...

11 Commits

Author SHA1 Message Date
Jędrzej Stuczyński d8958126e8 tmp: try mlkem 2026-01-26 15:04:28 +00:00
Georgio Nicolas fe45b856b7 make clippy happy 2026-01-26 15:16:29 +01:00
Georgio Nicolas 5c50b760a8 enable kkt with mceliece (kkt tests pass) 2026-01-26 15:11:08 +01:00
Georgio Nicolas aafa99ea47 kkt should work with different keys now 2026-01-26 15:01:43 +01:00
Georgio Nicolas 4767a719f7 impl hashing of different key types 2026-01-26 14:38:25 +01:00
Georgio Nicolas 89d167de08 add key input helper functions 2026-01-26 14:25:12 +01:00
Georgio Nicolas 05d5f4ae83 Merge remote-tracking branch 'origin' into georgio/lp-psqv2-integration 2026-01-26 13:28:40 +01:00
Georgio Nicolas 2109beeef6 Update keytypes and remove lifetime specifier for mceliece keys 2026-01-26 13:20:18 +01:00
Georgio Nicolas d1a5342625 Merge remote-tracking branch 'origin' into georgio/lp-psqv2-integration 2026-01-25 01:20:43 +01:00
Georgio Nicolas f0645bad57 Merge develop 2026-01-23 13:56:37 +01:00
Georgio Nicolas 60f8fe09a7 Introduce newer mlkem key types 2026-01-23 13:48:55 +01:00
13 changed files with 754 additions and 524 deletions
Generated
+470 -365
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -23,9 +23,15 @@ libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" }
rand = "0.9.2"
zeroize = { workspace = true, features = ["zeroize_derive"] }
classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] }
# classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] }
classic-mceliece-rust = { version = "3.1.0", features = [
"mceliece460896f",
"zeroize"] }
libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["classic-mceliece"] }
libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux"}
[dev-dependencies]
rand_chacha = "0.9.0"
anyhow = { workspace = true }
+6 -3
View File
@@ -12,7 +12,10 @@ use nym_kkt::{
ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM, SignatureScheme},
context::KKTMode,
frame::KKTFrame,
key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key},
key_utils::{
generate_keypair_libcrux, generate_keypair_mceliece, generate_keypair_mlkem,
hash_encapsulation_key,
},
session::{
anonymous_initiator_process, initiator_ingest_response, initiator_process,
responder_ingest_message, responder_process,
@@ -69,8 +72,8 @@ pub fn kkt_benchmark(c: &mut Criterion) {
let (responder_kem_public_key, initiator_kem_public_key) = match kem {
KEM::MlKem768 => (
EncapsulationKey::MlKem768(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
EncapsulationKey::MlKem768(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
),
KEM::XWing => (
EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
+38 -14
View File
@@ -3,23 +3,27 @@
use crate::error::KKTError;
use libcrux_kem::Algorithm;
use std::fmt::Debug;
pub use nym_kkt_ciphersuite::*;
pub enum EncapsulationKey<'a> {
pub enum EncapsulationKey {
MlKem768(libcrux_kem::PublicKey),
XWing(libcrux_kem::PublicKey),
X25519(libcrux_kem::PublicKey),
McEliece(classic_mceliece_rust::PublicKey<'a>),
McEliece(libcrux_psq::classic_mceliece::PublicKey),
}
pub enum DecapsulationKey<'a> {
MlKem768(libcrux_kem::PrivateKey),
XWing(libcrux_kem::PrivateKey),
X25519(libcrux_kem::PrivateKey),
McEliece(classic_mceliece_rust::SecretKey<'a>),
impl Debug for EncapsulationKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MlKem768(_) => f.debug_tuple("MlKem768").finish(),
Self::XWing(_) => f.debug_tuple("XWing").finish(),
Self::X25519(_) => f.debug_tuple("X25519").finish(),
Self::McEliece(_) => f.debug_tuple("McEliece").finish(),
}
}
}
impl<'a> EncapsulationKey<'a> {
impl EncapsulationKey {
pub(crate) fn decode(kem: KEM, bytes: &[u8]) -> Result<Self, KKTError> {
match kem {
KEM::McEliece => {
@@ -33,7 +37,7 @@ impl<'a> EncapsulationKey<'a> {
// Size must be correct due to KKTFrame::from_bytes(message_bytes)?
public_key_bytes.clone_from_slice(bytes);
Ok(EncapsulationKey::McEliece(
classic_mceliece_rust::PublicKey::from(public_key_bytes),
libcrux_psq::classic_mceliece::PublicKey::from(public_key_bytes),
))
}
}
@@ -54,10 +58,30 @@ impl<'a> EncapsulationKey<'a> {
pub fn encode(&self) -> Vec<u8> {
match self {
EncapsulationKey::XWing(public_key)
| EncapsulationKey::MlKem768(public_key)
| EncapsulationKey::X25519(public_key) => public_key.encode(),
EncapsulationKey::McEliece(public_key) => Vec::from(public_key.as_array()),
EncapsulationKey::XWing(public_key) | EncapsulationKey::X25519(public_key) => {
public_key.encode()
}
EncapsulationKey::McEliece(public_key) => {
let bytes_ref: &[u8] = public_key.as_ref();
Vec::from(bytes_ref)
}
EncapsulationKey::MlKem768(public_key) => Vec::from(public_key.encode()),
}
}
}
pub enum DecapsulationKey {
MlKem768(libcrux_kem::MlKem768PrivateKey),
XWing(libcrux_kem::PrivateKey),
X25519(libcrux_kem::PrivateKey),
McEliece(libcrux_psq::classic_mceliece::SecretKey),
}
impl Debug for DecapsulationKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MlKem768(_) => f.debug_tuple("MlKem768").finish(),
Self::XWing(_) => f.debug_tuple("XWing").finish(),
Self::X25519(_) => f.debug_tuple("X25519").finish(),
Self::McEliece(_) => f.debug_tuple("McEliece").finish(),
}
}
}
+3
View File
@@ -48,6 +48,9 @@ pub enum KKTError {
#[error("{}", info)]
AEADError { info: &'static str },
#[error("{}", info)]
DecodingError { info: &'static str },
#[error("Generic libcrux error")]
LibcruxError,
}
+15 -10
View File
@@ -1,8 +1,7 @@
use crate::ciphersuite::HashFunction;
use std::collections::HashMap;
use classic_mceliece_rust::keypair_boxed;
use libcrux_kem::{MlKem768PrivateKey, MlKem768PublicKey};
use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KEMKeyDigests};
use rand::{CryptoRng, RngCore};
@@ -38,9 +37,6 @@ where
R: RngCore + CryptoRng,
{
match kem {
crate::ciphersuite::KEM::MlKem768 => {
Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, rng)?)
}
crate::ciphersuite::KEM::XWing => Ok(libcrux_kem::key_gen(
libcrux_kem::Algorithm::XWingKemDraft06,
rng,
@@ -53,19 +49,28 @@ where
}),
}
}
pub fn generate_keypair_mlkem<R>(rng: &mut R) -> (MlKem768PrivateKey, MlKem768PublicKey)
where
R: RngCore + CryptoRng,
{
libcrux_ml_kem::mlkem768::rand::generate_key_pair(rng).into_parts()
}
// (decapsulation_key, encapsulation_key)
pub fn generate_keypair_mceliece<'a, R>(
pub fn generate_keypair_mceliece<R>(
rng: &mut R,
) -> (
classic_mceliece_rust::SecretKey<'a>,
classic_mceliece_rust::PublicKey<'a>,
libcrux_psq::classic_mceliece::SecretKey,
libcrux_psq::classic_mceliece::PublicKey,
)
where
// this is annoying because mceliece lib uses rand 0.8.5...
R: RngCore + CryptoRng,
{
let (encapsulation_key, decapsulation_key) = keypair_boxed(rng);
(decapsulation_key, encapsulation_key)
let kp = libcrux_psq::classic_mceliece::KeyPair::generate_key_pair(rng);
(kp.sk, kp.pk)
}
pub fn hash_key_bytes(
+3 -3
View File
@@ -98,13 +98,13 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
/// )?;
/// // Use gateway_kem_key for PSQ
/// ```
pub fn validate_kem_response<'a>(
pub fn validate_kem_response(
context: &mut KKTContext,
session_secret: &KKTSessionSecret,
responder_vk: &ed25519::PublicKey,
expected_key_hash: &[u8],
encrypted_response_bytes: &[u8],
) -> Result<EncapsulationKey<'a>, KKTError> {
) -> Result<EncapsulationKey, KKTError> {
let (responder_frame, responder_context) =
decrypt_kkt_response_frame(session_secret, encrypted_response_bytes)?;
@@ -159,7 +159,7 @@ pub fn handle_kem_request<'a, R>(
initiator_vk: Option<&ed25519::PublicKey>,
responder_signing_key: &ed25519::PrivateKey,
responder_dh_private_key: &x25519::PrivateKey,
responder_kem_key: &EncapsulationKey<'a>,
responder_kem_key: &EncapsulationKey,
) -> Result<Vec<u8>, KKTError>
where
R: RngCore + CryptoRng,
+5 -13
View File
@@ -28,7 +28,7 @@ mod test {
frame::KKTFrame,
key_utils::{
generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_mceliece,
generate_keypair_x25519, hash_encapsulation_key,
generate_keypair_mlkem, generate_keypair_x25519, hash_encapsulation_key,
},
session::{
anonymous_initiator_process, initiator_ingest_response, initiator_process,
@@ -63,12 +63,8 @@ mod test {
let (responder_kem_public_key, initiator_kem_public_key) = match kem {
KEM::MlKem768 => (
EncapsulationKey::MlKem768(
generate_keypair_libcrux(&mut rng, kem).unwrap().1,
),
EncapsulationKey::MlKem768(
generate_keypair_libcrux(&mut rng, kem).unwrap().1,
),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
),
KEM::XWing => (
EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
@@ -269,12 +265,8 @@ mod test {
let (responder_kem_public_key, initiator_kem_public_key) = match kem {
KEM::MlKem768 => (
EncapsulationKey::MlKem768(
generate_keypair_libcrux(&mut rng, kem).unwrap().1,
),
EncapsulationKey::MlKem768(
generate_keypair_libcrux(&mut rng, kem).unwrap().1,
),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1),
),
KEM::XWing => (
EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1),
+8 -8
View File
@@ -10,12 +10,12 @@ use crate::{
key_utils::validate_encapsulation_key,
};
pub fn initiator_process<'a, R>(
pub fn initiator_process<R>(
rng: &mut R,
mode: KKTMode,
ciphersuite: Ciphersuite,
signing_key: &ed25519::PrivateKey,
own_encapsulation_key: Option<&EncapsulationKey<'a>>,
own_encapsulation_key: Option<&EncapsulationKey>,
) -> Result<(KKTContext, KKTFrame), KKTError>
where
R: CryptoRng + RngCore,
@@ -72,13 +72,13 @@ where
Ok((context, KKTFrame::new(context_bytes, &[], session_id, &[])))
}
pub fn initiator_ingest_response<'a>(
pub fn initiator_ingest_response(
own_context: &mut KKTContext,
remote_frame: &KKTFrame,
remote_context: &KKTContext,
remote_verification_key: &ed25519::PublicKey,
expected_hash: &[u8],
) -> Result<EncapsulationKey<'a>, KKTError> {
) -> Result<EncapsulationKey, KKTError> {
check_compatibility(own_context, remote_context)?;
match remote_context.status() {
KKTStatus::Ok => {
@@ -124,12 +124,12 @@ pub fn initiator_ingest_response<'a>(
// todo: figure out how to handle errors using status codes
pub fn responder_ingest_message<'a>(
pub fn responder_ingest_message(
remote_context: &KKTContext,
remote_verification_key: Option<&ed25519::PublicKey>,
expected_hash: Option<&[u8]>,
remote_frame: &KKTFrame,
) -> Result<(KKTContext, Option<EncapsulationKey<'a>>), KKTError> {
) -> Result<(KKTContext, Option<EncapsulationKey>), KKTError> {
let own_context = remote_context.derive_responder_header()?;
match remote_context.role() {
@@ -200,11 +200,11 @@ pub fn responder_ingest_message<'a>(
}
}
pub fn responder_process<'a>(
pub fn responder_process(
own_context: &mut KKTContext,
session_id: KKTSessionId,
signing_key: &ed25519::PrivateKey,
encapsulation_key: &EncapsulationKey<'a>,
encapsulation_key: &EncapsulationKey,
) -> Result<KKTFrame, KKTError> {
let body = encapsulation_key.encode();
+92 -16
View File
@@ -1,14 +1,16 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use libcrux_kem::{MlKem768PrivateKey, MlKem768PublicKey};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_kkt::ciphersuite::{HashFunction, KEM};
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey, HashFunction, KEM};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
/// Representation of a local Lewes Protocol peer
/// encapsulating all the known information and keys.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct LpLocalPeer {
/// Local Ed25519 keys for PSQ authentication
pub(crate) ed25519: Arc<ed25519::KeyPair>,
@@ -16,8 +18,14 @@ pub struct LpLocalPeer {
/// Local x25519 keys (Noise static key)
pub(crate) x25519: Arc<x25519::KeyPair>,
/// Local KEM key used for PSQ
/// Local KEM key used for PSQ (x25519: to deprecate)
pub(crate) kem_psq: Option<Arc<x25519::KeyPair>>,
/// Local MlKem keypair used for PSQ
pub(crate) mlkem: Option<(Arc<DecapsulationKey>, Arc<EncapsulationKey>)>,
/// Local McEliece keypair used for PSQ
pub(crate) mceliece: Option<(Arc<DecapsulationKey>, Arc<EncapsulationKey>)>,
}
impl LpLocalPeer {
@@ -26,15 +34,43 @@ impl LpLocalPeer {
ed25519,
x25519,
kem_psq: None,
mlkem: None,
mceliece: None,
}
}
#[must_use]
// #[must_use]
pub fn with_kem_psq_key(mut self, key: Arc<x25519::KeyPair>) -> Self {
self.kem_psq = Some(key);
self
}
pub fn with_mlkem_keypair(
mut self,
decapsulation_key: &MlKem768PrivateKey,
encapsulation_key: &MlKem768PublicKey,
) -> Self {
self.mlkem = Some((
Arc::new(DecapsulationKey::MlKem768(decapsulation_key.clone())),
Arc::new(EncapsulationKey::MlKem768(
libcrux_kem::PublicKey::MlKem768(encapsulation_key.clone()),
)),
));
self
}
pub fn with_mceliece_keypair(
mut self,
decapsulation_key: libcrux_psq::classic_mceliece::SecretKey,
encapsulation_key: libcrux_psq::classic_mceliece::PublicKey,
) -> Self {
self.mceliece = Some((
Arc::new(DecapsulationKey::McEliece(decapsulation_key)),
Arc::new(EncapsulationKey::McEliece(encapsulation_key)),
));
self
}
pub fn ed25519(&self) -> &Arc<ed25519::KeyPair> {
&self.ed25519
}
@@ -46,17 +82,28 @@ impl LpLocalPeer {
/// Convert this `LpLocalPeer` into a valid `LpRemotePeer` that can be used within tests
#[doc(hidden)]
pub fn as_remote(&self) -> LpRemotePeer {
let expected_kem_key_digests = match &self.kem_psq {
None => HashMap::new(),
Some(kem_keys) => {
let hashes =
nym_kkt::key_utils::produce_key_digests(kem_keys.public_key().as_bytes());
let mut expected_kem_key_digests = HashMap::new();
if let Some(x25519_key) = &self.kem_psq {
expected_kem_key_digests.insert(
KEM::X25519,
nym_kkt::key_utils::produce_key_digests(x25519_key.public_key().as_bytes()),
);
}
if let Some(mlkem_key) = &self.mlkem {
expected_kem_key_digests.insert(
KEM::MlKem768,
nym_kkt::key_utils::produce_key_digests(&mlkem_key.1.encode()),
);
}
if let Some(mceliece_key) = &self.mceliece {
expected_kem_key_digests.insert(
KEM::McEliece,
nym_kkt::key_utils::produce_key_digests(&mceliece_key.1.encode()),
);
}
let mut digests = HashMap::new();
digests.insert(KEM::X25519, hashes);
digests
}
};
LpRemotePeer {
ed25519_public: *self.ed25519.public_key(),
x25519_public: *self.x25519.public_key(),
@@ -66,7 +113,7 @@ impl LpLocalPeer {
// this is only exposed in tests as ideally we should be storing the proper types to begin with
#[cfg(test)]
pub fn encapsulate_kem_key(&self) -> Option<nym_kkt::ciphersuite::EncapsulationKey<'_>> {
pub fn encapsulate_kem_key(&self) -> Option<nym_kkt::ciphersuite::EncapsulationKey> {
let pk_bytes = self.kem_psq.as_ref()?.public_key().to_bytes();
let libcrux_pk =
libcrux_kem::PublicKey::decode(libcrux_kem::Algorithm::X25519, &pk_bytes).ok()?;
@@ -75,6 +122,26 @@ impl LpLocalPeer {
}
}
impl Debug for LpLocalPeer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LpLocalPeer")
.field("ed25519", &self.ed25519)
.field("x25519", &self.x25519)
.field("kem_psq", &self.kem_psq)
.field(
"mlkem",
&format!(
"mlkem_public_key: {}",
match &self.mlkem {
Some(keypair) => format!("{:?}", keypair.1.as_ref()),
None => "None".to_string(),
}
),
)
.finish()
}
}
/// Representation of a remote Lewes Protocol peer
/// encapsulating all the known information and keys.
#[derive(Debug, Clone)]
@@ -124,16 +191,25 @@ pub fn mock_peer() -> LpLocalPeer {
}
#[cfg(test)]
pub fn random_peer<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> LpLocalPeer {
pub fn random_peer<'a, R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> LpLocalPeer {
use nym_kkt::key_utils::{generate_keypair_mceliece, generate_keypair_mlkem};
let ed25519 = Arc::new(ed25519::KeyPair::new(rng));
let x25519 = Arc::new(ed25519.to_x25519());
let kem_psq = Some(x25519.clone());
let mlkem_keypair = generate_keypair_mlkem(&mut rand09::rng());
let mceliece_keypair = generate_keypair_mceliece(&mut rand09::rng());
LpLocalPeer {
ed25519,
x25519,
kem_psq,
mlkem: None,
mceliece: None,
}
.with_mlkem_keypair(&mlkem_keypair.0, &mlkem_keypair.1)
.with_mceliece_keypair(mceliece_keypair.0, mceliece_keypair.1)
}
#[cfg(test)]
+57 -36
View File
@@ -48,7 +48,9 @@
use crate::LpError;
use libcrux_psq::v1::cred::{Authenticator, Ed25519};
use libcrux_psq::v1::impls::MlKem768 as PsqMlKem768;
use libcrux_psq::v1::impls::X25519 as PsqX25519;
use libcrux_psq::v1::impls::XWingKemDraft06 as PsqXwing;
use libcrux_psq::v1::psk_registration::{Initiator, InitiatorMsg, Responder};
use libcrux_psq::v1::traits::{Ciphertext as PsqCiphertext, PSQ};
use nym_crypto::asymmetric::{ed25519, x25519};
@@ -290,64 +292,83 @@ pub fn psq_initiator_create_message(
// Step 1: Classical ECDH for baseline security
let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public);
// Step 2: PSQ v1 with Ed25519 authentication
// Extract X25519 KEM key from EncapsulationKey
let kem_pk = match remote_kem_public {
EncapsulationKey::X25519(pk) => pk,
_ => {
return Err(LpError::KKTError(
"Only X25519 KEM is currently supported for PSQ".to_string(),
));
}
};
// Convert nym Ed25519 keys to libcrux format
type Ed25519VerificationKey = <Ed25519 as Authenticator>::VerificationKey;
let ed25519_sk_bytes = client_ed25519_sk.to_bytes();
let ed25519_pk_bytes = client_ed25519_pk.to_bytes();
let ed25519_verification_key = Ed25519VerificationKey::from_bytes(ed25519_pk_bytes);
// Use PSQ v1 API with Ed25519 authentication
let mut rng = rand09::rng();
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqX25519>(
session_context,
Duration::from_secs(3600), // 1 hour expiry
kem_pk,
&ed25519_sk_bytes,
&ed25519_verification_key,
&mut rng,
)
.map_err(|e| {
let init_msg_err = |e: libcrux_psq::v1::Error| {
tracing::error!(
"PSQ initiator failed - KEM encapsulation or signing error: {:?}",
e
);
LpError::Internal(format!("PSQ v1 send_initial_message failed: {:?}", e))
})?;
};
// Extract PSQ shared secret (unregistered PSK) - this is K_pq
let psq_psk = state.unregistered_psk();
let serialisation_err =
|e| LpError::Internal(format!("InitiatorMsg serialization failed: {:?}", e));
// pq_shared_secret is the raw K_pq from KEM encapsulation.
// Store it for subsession derivation before it's combined with ECDH.
let pq_shared_secret: [u8; 32] = *psq_psk;
let ctx = session_context;
let sk = &ed25519_sk_bytes;
let vk = &ed25519_verification_key;
let exp = Duration::from_secs(3600); // 1 hour expiry
// Step 2: PSQ v1 with Ed25519 authentication
// Use PSQ v1 API with Ed25519 authentication
let mut rng = rand09::rng();
// psq_psk - this is K_pq
let (pq_shared_secret, payload) = match remote_kem_public {
EncapsulationKey::X25519(kem_pk) => {
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqX25519>(
ctx, exp, kem_pk, sk, vk, &mut rng,
)
.map_err(init_msg_err)?;
let msg_bytes = initiator_msg
.tls_serialize_detached()
.map_err(serialisation_err)?;
(*state.unregistered_psk(), msg_bytes)
}
EncapsulationKey::MlKem768(kem_pk) => {
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqMlKem768>(
ctx, exp, kem_pk, sk, vk, &mut rng,
)
.map_err(init_msg_err)?;
let msg_bytes = initiator_msg
.tls_serialize_detached()
.map_err(serialisation_err)?;
(*state.unregistered_psk(), msg_bytes)
}
EncapsulationKey::XWing(kem_pk) => {
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqXwing>(
ctx, exp, kem_pk, sk, vk, &mut rng,
)
.map_err(init_msg_err)?;
let msg_bytes = initiator_msg
.tls_serialize_detached()
.map_err(serialisation_err)?;
(*state.unregistered_psk(), msg_bytes)
}
EncapsulationKey::McEliece(pk) => {
todo!("unsupported mceliece for PSQ")
}
};
// Step 3: Combine ECDH + PSQ via Blake3 KDF
let mut combined = Vec::with_capacity(64 + psq_psk.len());
let mut combined = Vec::with_capacity(64 + pq_shared_secret.len());
combined.extend_from_slice(&ecdh_secret);
combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32]
combined.extend_from_slice(&pq_shared_secret); // psq_psk is already a &[u8; 32]
combined.extend_from_slice(salt);
let final_psk = nym_crypto::kdf::derive_key_blake3(PSK_PSQ_CONTEXT, &combined, &[]);
// Serialize InitiatorMsg with TLS encoding for transport
let msg_bytes = initiator_msg
.tls_serialize_detached()
.map_err(|e| LpError::Internal(format!("InitiatorMsg serialization failed: {:?}", e)))?;
Ok(PsqInitiatorResult {
psk: final_psk,
payload: msg_bytes,
payload,
pq_shared_secret,
})
}
+47 -31
View File
@@ -19,7 +19,7 @@ use crate::replay::ReceivingKeyCounterValidator;
use crate::{LpError, LpMessage, LpPacket};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_kkt::KKT_RESPONSE_AAD;
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey};
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey, KEM};
use nym_kkt::context::KKTContext;
use nym_kkt::encryption::{
KKTSessionSecret, decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame,
@@ -32,6 +32,7 @@ use nym_kkt::session::{
use parking_lot::Mutex;
use rand::RngCore;
use snow::Builder;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -90,7 +91,7 @@ pub enum KKTState {
/// KKT exchange completed (initiator received and validated KEM key).
Completed {
/// Responder's KEM public key for PSQ encapsulation
kem_pk: Box<EncapsulationKey<'static>>,
kem_pk: Box<EncapsulationKey>,
},
/// Responder processed a KKT request and sent response.
@@ -303,6 +304,14 @@ impl LpSession {
self.remote_peer.ed25519_public
}
pub fn local_kem_encapsulation_key(&self, kem: &KEM) -> Option<Arc<EncapsulationKey>> {
match kem {
KEM::MlKem768 => self.local_peer.mlkem.as_ref().map(|x| x.1.clone()),
KEM::McEliece => self.local_peer.mceliece.as_ref().map(|x| x.1.clone()),
_ => None,
}
}
/// Returns the remote X25519 public key.
///
/// Used for tie-breaking in simultaneous subsession initiation.
@@ -538,9 +547,7 @@ impl LpSession {
// TODO: missing Zeroize
/// Convert local KEM PSQ keys to typed EncapsulationKey / EncapsulationKey
pub fn encapsulated_kem_keys(
&self,
) -> Result<(DecapsulationKey<'_>, EncapsulationKey<'_>), LpError> {
pub fn encapsulated_kem_keys(&self) -> Result<(DecapsulationKey, EncapsulationKey), LpError> {
let kem_keys = self
.local_peer
.kem_psq
@@ -599,7 +606,7 @@ impl LpSession {
// georgio this needs to be moved one level up (maybe chosen in LPSession)
let ciphersuite = match Ciphersuite::resolve_ciphersuite(
KEM::X25519,
KEM::MlKem768,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
@@ -743,11 +750,7 @@ impl LpSession {
///
/// * `Ok(LpMessage::KKTResponse)` - Signed KKT response ready to send
/// * `Err(LpError)` - Signature verification failed or invalid request
pub fn process_kkt_request(
&self,
request_bytes: &[u8],
responder_kem_pk: &EncapsulationKey,
) -> Result<LpMessage, LpError> {
pub fn process_kkt_request(&self, request_bytes: &[u8]) -> Result<LpMessage, LpError> {
let mut rng = rand09::rng();
let mut kkt_state = self.kkt_state.lock();
@@ -758,33 +761,46 @@ impl LpSession {
) {
Ok((session_secret, request_frame, remote_context)) => {
match responder_ingest_message(&remote_context, None, None, &request_frame) {
Ok((mut context, _)) => match responder_process(
&mut context,
request_frame.session_id(),
self.local_peer.ed25519().private_key(),
responder_kem_pk,
) {
Ok(response_frame) => match encrypt_kkt_frame(
&mut rng,
&session_secret,
&response_frame,
KKT_RESPONSE_AAD,
Ok((mut context, _)) => {
let local_kem_key: Arc<EncapsulationKey> =
match self.local_kem_encapsulation_key(&context.ciphersuite().kem()) {
Some(key) => key.clone(),
None => {
return Err(LpError::Internal(format!(
"KEM key for algorithm ({:?}) is not available.",
&context.ciphersuite().kem()
)));
}
};
match responder_process(
&mut context,
request_frame.session_id(),
self.local_peer.ed25519().private_key(),
&local_kem_key,
) {
Ok(response_bytes) => response_bytes,
Ok(response_frame) => match encrypt_kkt_frame(
&mut rng,
&session_secret,
&response_frame,
KKT_RESPONSE_AAD,
) {
Ok(response_bytes) => response_bytes,
Err(e) => {
return Err(LpError::Internal(format!(
"KKT response encryption failure : {:?}",
e
)));
}
},
Err(e) => {
return Err(LpError::Internal(format!(
"KKT response encryption failure : {:?}",
"KKT response generation failure : {:?}",
e
)));
}
},
Err(e) => {
return Err(LpError::Internal(format!(
"KKT response generation failure : {:?}",
e
)));
}
},
}
Err(e) => {
return Err(LpError::Internal(format!(
"KKT request handling failure: {:?}",
+3 -24
View File
@@ -360,22 +360,7 @@ impl LpStateMachine {
// Packet message is already parsed, match on it directly
match &packet.message {
LpMessage::KKTRequest(kkt_request) if !session.is_initiator() => {
// Responder processes KKT request
// Convert X25519 public key to KEM format for KKT response
use nym_kkt::ciphersuite::EncapsulationKey;
// Get local X25519 public key by deriving from private key
let local_x25519_public = session.local_x25519_public();
// Convert to libcrux KEM public key
match libcrux_kem::PublicKey::decode(
libcrux_kem::Algorithm::X25519,
local_x25519_public.as_bytes(),
) {
Ok(libcrux_public_key) => {
let responder_kem_pk = EncapsulationKey::X25519(libcrux_public_key);
match session.process_kkt_request(&kkt_request.0, &responder_kem_pk) {
match session.process_kkt_request(&kkt_request.0) {
Ok(kkt_response_message) => {
match session.next_packet(kkt_response_message) {
Ok(response_packet) => {
@@ -396,14 +381,8 @@ impl LpStateMachine {
LpState::Closed { reason }
}
}
}
Err(e) => {
let reason = format!("Failed to convert X25519 to KEM: {:?}", e);
let err = LpError::Internal(reason.clone());
result_action = Some(Err(err));
LpState::Closed { reason }
}
}
}
LpMessage::KKTResponse(kkt_response) if session.is_initiator() => {
match session.process_kkt_response(&kkt_response.0) {