Blake3 hkdf + integration
This commit is contained in:
Generated
+2
@@ -4137,6 +4137,7 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-gateway",
|
||||
"nym-kkt-ciphersuite",
|
||||
"nym-lp-transport",
|
||||
"nym-registration-client",
|
||||
"nym-test-utils",
|
||||
@@ -6224,6 +6225,7 @@ dependencies = [
|
||||
"nym-sphinx-types",
|
||||
"nym-test-utils",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
|
||||
@@ -320,6 +320,7 @@ publicsuffix = "2.3.0"
|
||||
proc_pidinfo = "0.1.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand09 = { package = "rand", version = "0.9.2" }
|
||||
rand_chacha = "0.3"
|
||||
rand_core = "0.6.3"
|
||||
rand_distr = "0.4"
|
||||
|
||||
@@ -13,14 +13,14 @@ num_enum = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "hashing", "serde"] }
|
||||
nym-kkt-ciphersuite = { workspace = true, features = ["digests"] }
|
||||
|
||||
libcrux-kem = { workspace = true }
|
||||
libcrux-ecdh = { workspace = true, features = ["codec"] }
|
||||
libcrux-chacha20poly1305 = { workspace = true }
|
||||
|
||||
rand = "0.9.2"
|
||||
rand09 = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
libcrux-psq = { workspace = true, features = ["classic-mceliece"] }
|
||||
|
||||
@@ -21,13 +21,13 @@ use nym_kkt::{
|
||||
responder_ingest_message, responder_process,
|
||||
},
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rand09::prelude::*;
|
||||
|
||||
pub fn gen_ed25519_keypair(c: &mut Criterion) {
|
||||
c.bench_function("Generate Ed25519 Keypair", |b| {
|
||||
b.iter(|| {
|
||||
let mut s: [u8; 32] = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut s);
|
||||
rand09::rng().fill_bytes(&mut s);
|
||||
ed25519::KeyPair::from_secret(s, 0)
|
||||
});
|
||||
});
|
||||
@@ -36,13 +36,13 @@ pub fn gen_ed25519_keypair(c: &mut Criterion) {
|
||||
pub fn gen_mlkem768_keypair(c: &mut Criterion) {
|
||||
c.bench_function("Generate MlKem768 Keypair", |b| {
|
||||
b.iter(|| {
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand::rng()).unwrap()
|
||||
libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, &mut rand09::rng()).unwrap()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn kkt_benchmark(c: &mut Criterion) {
|
||||
let mut rng = rand::rng();
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// generate ed25519 keys
|
||||
let mut secret_initiator: [u8; 32] = [0u8; 32];
|
||||
|
||||
@@ -5,8 +5,9 @@ use crate::{KKT_INITIAL_FRAME_AAD, context::KKTContext, error::KKTError, frame::
|
||||
use blake3::Hasher;
|
||||
use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN};
|
||||
use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey};
|
||||
use nym_crypto::hkdf::blake3::derive_key_blake3;
|
||||
use nym_kkt_ciphersuite::x25519;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand09::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Clone, Copy, Zeroize)]
|
||||
@@ -49,15 +50,14 @@ impl KKTSessionSecret {
|
||||
.map_err(|_| KKTError::X25519Error {
|
||||
info: "Key Derivation Error",
|
||||
})?;
|
||||
|
||||
let mut hasher = Hasher::new();
|
||||
|
||||
hasher.update(shared_secret.as_ref());
|
||||
Ok(Self(derive_key_blake3(
|
||||
"KKT_KDF",
|
||||
shared_secret.as_ref(),
|
||||
&[],
|
||||
)))
|
||||
|
||||
// TODO: zeroize
|
||||
// shared_secret.zeroize();
|
||||
|
||||
Ok(Self(hasher.finalize().as_bytes().to_owned()))
|
||||
}
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
@@ -191,8 +191,8 @@ mod test {
|
||||
encryption::{KKTSessionSecret, decrypt, encrypt},
|
||||
key_utils::generate_keypair_x25519,
|
||||
};
|
||||
use rand::{RngCore, SeedableRng, rng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use rand09::{RngCore, SeedableRng, rng};
|
||||
|
||||
#[test]
|
||||
fn test_keygen() {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||
use libcrux_kem::{MlKem768PrivateKey, MlKem768PublicKey};
|
||||
use libcrux_psq::handshake::types::DHKeyPair;
|
||||
use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KeyDigests};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand09::{CryptoRng, RngCore};
|
||||
|
||||
pub fn generate_keypair_ed25519<R>(
|
||||
rng: &mut R,
|
||||
|
||||
@@ -8,16 +8,6 @@ pub mod error;
|
||||
pub mod frame;
|
||||
pub mod key_utils;
|
||||
// pub mod kkt;
|
||||
/// This module implements a stateless post-quantum re-keying protocol in one round-trip.
|
||||
/// We currently support MlKem768 and XWing.
|
||||
///
|
||||
/// This protocol is safe if it runs under a trusted secure channel.
|
||||
///
|
||||
/// Bandwidth costs:
|
||||
/// Request (MlKem768): 1216 bytes
|
||||
/// Response (MlKem768): 1088 bytes
|
||||
/// Request (XWing): 1248 bytes
|
||||
/// Response (XWing): 1120 bytes
|
||||
pub mod rekey;
|
||||
pub mod session;
|
||||
|
||||
@@ -49,7 +39,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_kkt_psq_e2e_clear() {
|
||||
let mut rng = rand::rng();
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// generate ed25519 keys
|
||||
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
|
||||
@@ -248,7 +238,7 @@ mod test {
|
||||
}
|
||||
#[test]
|
||||
fn test_kkt_psq_e2e_encrypted() {
|
||||
let mut rng = rand::rng();
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// generate ed25519 keys
|
||||
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
|
||||
|
||||
+51
-39
@@ -1,24 +1,39 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Post-Quantum Re-Key Protocol
|
||||
/// This module implements a stateless post-quantum re-keying protocol in one round-trip.
|
||||
/// We currently support MlKem768 and XWing.
|
||||
///
|
||||
/// This protocol is safe if it runs under a trusted secure channel.
|
||||
///
|
||||
/// Bandwidth costs:
|
||||
/// Request (MlKem768): 1216 bytes
|
||||
/// Response (MlKem768): 1088 bytes
|
||||
/// Request (XWing): 1248 bytes
|
||||
/// Response (XWing): 1120 bytes
|
||||
use libcrux_kem::*;
|
||||
use nym_crypto::hkdf::blake3::derive_key_blake3;
|
||||
use nym_kkt_ciphersuite::{KEM, mceliece, ml_kem768, x25519, xwing};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand09::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::error::KKTError;
|
||||
|
||||
/// Context string to be used with the Blake3 KDF.
|
||||
const REKEY_CONTEXT: &str = "NYM_REKEY_v1";
|
||||
const REKEY_CONTEXT: &str = "NYM_PQ_REKEY_v1";
|
||||
|
||||
pub struct RekeyInitiator {
|
||||
algorithm: Algorithm,
|
||||
decapsulation_key: PrivateKey,
|
||||
randomness: [u8; 32],
|
||||
salt: [u8; 32],
|
||||
}
|
||||
|
||||
impl RekeyInitiator {
|
||||
/// The Initiator generates an ephemeral KEM keypair and 32 bytes of randomness.
|
||||
/// The Initiator generates an ephemeral KEM keypair and a 32-byte salt.
|
||||
/// The Initiator keeps the decapsulation key and generates a request message.
|
||||
/// The request message contains the random 32 bytes and an encoding of the encapsulation key as follows
|
||||
/// randomness encapsulation_key
|
||||
/// The request message contains the salt and an encoding of the encapsulation key as follows
|
||||
/// salt encapsulation_key
|
||||
/// [0 ........ 32 | 32 .............. ]
|
||||
///
|
||||
/// Inputs:
|
||||
@@ -26,22 +41,15 @@ impl RekeyInitiator {
|
||||
/// kem: a KEM algorithm (we currently support MlKem768 and XWing)
|
||||
///
|
||||
/// Outputs:
|
||||
/// RekeyInitiator: A struct which contains the decapsulation key, the generated randomness and the kem algorithm in use.
|
||||
/// RekeyInitiator: A struct which contains the decapsulation key, the salt and the kem algorithm in use.
|
||||
/// Vec<u8>: The request message as explained above. This is to be sent to the responder as-is.
|
||||
pub fn generate_request<R>(rng: &mut R, kem: KEM) -> Result<(RekeyInitiator, Vec<u8>), KKTError>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
// Generate the Initiator's randomness.
|
||||
let mut randomness = [0u8; 32];
|
||||
rng.fill_bytes(&mut randomness);
|
||||
|
||||
// Create the buffer for the request message and copy the randomness bytes into it.
|
||||
let mut request_buffer: Vec<u8> = randomness.to_vec();
|
||||
|
||||
let algorithm = match kem {
|
||||
KEM::XWing => Algorithm::XWingKemDraft06,
|
||||
KEM::MlKem768 => Algorithm::MlKem768,
|
||||
let (algorithm, buffer_size) = match kem {
|
||||
KEM::XWing => (Algorithm::XWingKemDraft06, 32 + xwing::PUBLIC_KEY_LENGTH),
|
||||
KEM::MlKem768 => (Algorithm::MlKem768, 32 + ml_kem768::PUBLIC_KEY_LENGTH),
|
||||
// We don't support McEliece because the keys are massive.
|
||||
// If this is a deal-breaker, users can start a new session with PSQ which can use McEliece.
|
||||
KEM::McEliece => {
|
||||
@@ -57,6 +65,14 @@ impl RekeyInitiator {
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the Initiator's salt
|
||||
let mut salt = [0u8; 32];
|
||||
rng.fill_bytes(&mut salt);
|
||||
|
||||
// Create the buffer for the request message and copy the salt into it.
|
||||
let mut request_buffer = Vec::with_capacity(buffer_size);
|
||||
request_buffer.extend_from_slice(&salt);
|
||||
|
||||
// Generate the ephemeral KEM keypair based on the algorithm from the function's input.
|
||||
let (decapsulation_key, encapsulation_key) = key_gen(algorithm, rng)?;
|
||||
|
||||
@@ -68,7 +84,7 @@ impl RekeyInitiator {
|
||||
RekeyInitiator {
|
||||
algorithm,
|
||||
decapsulation_key,
|
||||
randomness,
|
||||
salt,
|
||||
},
|
||||
// This is to be sent to the responder.
|
||||
request_buffer,
|
||||
@@ -76,7 +92,7 @@ impl RekeyInitiator {
|
||||
}
|
||||
|
||||
/// The Initiator will attempt to decapsulate the `pre_key` generated by the responder
|
||||
/// secret. This `pre_key` will be combined with the Initiator's previously generated randomness
|
||||
/// secret. This `pre_key` will be combined with the Initiator's previously generated salt
|
||||
/// as input to a Blake3 KDF call to generate the new shared secret.
|
||||
///
|
||||
/// This function fails if the ciphertext cannot be decoded or decapsulated.
|
||||
@@ -92,14 +108,12 @@ impl RekeyInitiator {
|
||||
let pre_key = ciphertext.decapsulate(&self.decapsulation_key)?;
|
||||
|
||||
// Encode the `pre_key` into bytes
|
||||
let mut to_hash = pre_key.encode();
|
||||
// Concatenate the Initiator's randomness to those bytes.
|
||||
to_hash.extend_from_slice(&self.randomness);
|
||||
// Derive a the new secret key: Blake3_KDF(pre_key || initiator_randomess)
|
||||
let new_secret: [u8; 32] = blake3::derive_key(REKEY_CONTEXT, &to_hash);
|
||||
let pre_key_bytes = pre_key.encode();
|
||||
|
||||
// Zeroize the Initiator's randomness
|
||||
self.randomness.zeroize();
|
||||
let new_secret: [u8; 32] = derive_key_blake3(REKEY_CONTEXT, &pre_key_bytes, &self.salt);
|
||||
|
||||
// Zeroize the Initiator's salt
|
||||
self.salt.zeroize();
|
||||
|
||||
// TODO: zeroize the decapsulation key
|
||||
|
||||
@@ -108,7 +122,7 @@ impl RekeyInitiator {
|
||||
}
|
||||
|
||||
/// The responder parses the request message.
|
||||
/// The first 32 bytes are the Initiator's randomness,
|
||||
/// The first 32 bytes are the Initiator's salt,
|
||||
/// and the remainder is the encoding of the public key.
|
||||
/// Given that XWing and MlKem768 have different key lengths,
|
||||
/// we could deduce the algorithm from that.
|
||||
@@ -117,12 +131,12 @@ impl RekeyInitiator {
|
||||
/// this function will produce an error.
|
||||
///
|
||||
/// If everything is alright, the responder generates and encapsulates a key `pre_key` to send to the Initiator.
|
||||
/// Then, the responder calls a Blake3 KDF over `pre_key` and the Initiator's randomness to obtain
|
||||
/// Then, the responder calls a Blake3 KDF over `pre_key` and the Initiator's salt to obtain
|
||||
/// the new shared secret.
|
||||
///
|
||||
/// Inputs:
|
||||
/// rng: something that implements CryptoRng + RngCore
|
||||
/// request_message: the Initiator's request message (contains randomness and an encapsulation key)
|
||||
/// request_message: the Initiator's request message (contains the salt and encapsulation key)
|
||||
///
|
||||
/// Outputs:
|
||||
/// [u8; 32]: new shared secret
|
||||
@@ -171,9 +185,9 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
// Split the message to get the Initiator's randomness (first 32 bytes)
|
||||
// Split the message to get the Initiator's salt (first 32 bytes)
|
||||
// and the encoding of the Initiator's public key.
|
||||
let (remote_randomness, remote_encapsulation_key_bytes) = request_message.split_at_mut(32);
|
||||
let (remote_salt, remote_encapsulation_key_bytes) = request_message.split_at_mut(32);
|
||||
|
||||
// Attempt to decode the Initiator's encapsulation key.
|
||||
let remote_encapsulation_key = PublicKey::decode(algorithm, remote_encapsulation_key_bytes)?;
|
||||
@@ -184,14 +198,12 @@ where
|
||||
let message = ciphertext.encode();
|
||||
|
||||
// Encode the `pre_key` into bytes
|
||||
let mut to_hash = pre_key.encode();
|
||||
// Concatenate the Initiator's randomness to those bytes.
|
||||
to_hash.extend_from_slice(remote_randomness);
|
||||
// Derive a the new secret key: Blake3_KDF(pre_key || initiator_randomness)
|
||||
let new_secret: [u8; 32] = blake3::derive_key(REKEY_CONTEXT, &to_hash);
|
||||
let pre_key_bytes = pre_key.encode();
|
||||
|
||||
// Zeroize the Initiator's randomness
|
||||
remote_randomness.zeroize();
|
||||
let new_secret: [u8; 32] = derive_key_blake3(REKEY_CONTEXT, &pre_key_bytes, &remote_salt);
|
||||
|
||||
// Zeroize the Initiator's salt
|
||||
remote_salt.zeroize();
|
||||
|
||||
Ok((new_secret, message))
|
||||
}
|
||||
@@ -203,7 +215,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rekey_test() {
|
||||
let mut rng = rand::rng();
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
for kem in [KEM::MlKem768, KEM::XWing] {
|
||||
let (rekey_state, request_message) =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use nym_crypto::asymmetric::ed25519::{self, Signature};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand09::{CryptoRng, RngCore};
|
||||
|
||||
use crate::frame::KKTSessionId;
|
||||
use crate::{
|
||||
|
||||
Reference in New Issue
Block a user