LP: modified LPRemotePeer to dynamically choose required KEM key hash (#6358)
* LP: modified LPRemotePeer to dynamically choose required KEM key hash * nym-lp-client fixes
This commit is contained in:
committed by
GitHub
parent
43d1c61b70
commit
a63a1e745e
Generated
+17
-1
@@ -6796,15 +6796,28 @@ dependencies = [
|
||||
"libcrux-chacha20poly1305",
|
||||
"libcrux-ecdh",
|
||||
"libcrux-kem",
|
||||
"libcrux-sha3",
|
||||
"num_enum",
|
||||
"nym-crypto",
|
||||
"nym-kkt-ciphersuite",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"strum",
|
||||
"thiserror 2.0.17",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-kkt-ciphersuite"
|
||||
version = "1.20.1"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"libcrux-sha3",
|
||||
"num_enum",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ledger"
|
||||
version = "1.20.1"
|
||||
@@ -6858,6 +6871,7 @@ dependencies = [
|
||||
"nym-http-api-client",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-kcp",
|
||||
"nym-kkt-ciphersuite",
|
||||
"nym-lp",
|
||||
"nym-registration-client",
|
||||
"nym-sphinx",
|
||||
@@ -7197,6 +7211,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-exit-policy",
|
||||
"nym-http-api-client",
|
||||
"nym-kkt-ciphersuite",
|
||||
"nym-noise-keys",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-wireguard-types",
|
||||
@@ -7503,6 +7518,7 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-kkt-ciphersuite",
|
||||
"nym-sphinx",
|
||||
"nym-wireguard-types",
|
||||
"serde",
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ members = [
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
"nym-gateway-probe",
|
||||
"integration-tests", "common/nym-lp-transport",
|
||||
"integration-tests", "common/nym-lp-transport", "common/nym-kkt-ciphersuite",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "nym-kkt-ciphersuite"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
num_enum = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
|
||||
blake3 = { workspace = true, optional = true }
|
||||
libcrux-sha3 = { git = "https://github.com/cryspen/libcrux", optional = true }
|
||||
|
||||
[features]
|
||||
digests = ["blake3", "libcrux-sha3"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum KKTCiphersuiteError {
|
||||
#[error(
|
||||
"attempted to use an insecure encapsulation key hash length. requested: {requested}. minimum: {minimum}"
|
||||
)]
|
||||
InsecureHashLen { requested: u8, minimum: u8 },
|
||||
|
||||
#[error("{raw} does not correspond to any known KEM type encoding")]
|
||||
UnknownKEMType { raw: u8 },
|
||||
|
||||
#[error("{raw} does not correspond to any known Hash Function type encoding")]
|
||||
UnknownHashFunctionType { raw: u8 },
|
||||
|
||||
#[error("{raw} does not correspond to any known Signature Scheme type encoding")]
|
||||
UnknownSignatureSchemeType { raw: u8 },
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::KKTCiphersuiteError;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub const DEFAULT_HASH_LEN: usize = 32;
|
||||
const _: () = assert!(DEFAULT_HASH_LEN <= u8::MAX as usize);
|
||||
|
||||
pub const MINIMUM_SECURE_HASH_LEN: u8 = 16;
|
||||
const _: () = assert!(MINIMUM_SECURE_HASH_LEN <= DEFAULT_HASH_LEN as u8);
|
||||
|
||||
pub const CIPHERSUITE_ENCODING_LEN: usize = 4;
|
||||
|
||||
// no point in importing curve libraries for well-defined constants
|
||||
pub mod ed25519 {
|
||||
pub const SECRET_KEY_LENGTH: usize = 32;
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 32;
|
||||
pub const SIGNATURE_LENGTH: usize = 64;
|
||||
}
|
||||
|
||||
pub mod x25519 {
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 32;
|
||||
pub const SECRET_KEY_LENGTH: usize = 32;
|
||||
}
|
||||
|
||||
pub mod ml_kem768 {
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 1184;
|
||||
}
|
||||
|
||||
pub mod mceliece {
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 524160;
|
||||
pub const SECRET_KEY_LENGTH: usize = 13608;
|
||||
pub const CIPHERTEXT_LENGTH: usize = 156;
|
||||
}
|
||||
|
||||
pub mod xwing {
|
||||
use crate::{ml_kem768, x25519};
|
||||
|
||||
pub const PUBLIC_KEY_LENGTH: usize = x25519::PUBLIC_KEY_LENGTH + ml_kem768::PUBLIC_KEY_LENGTH;
|
||||
}
|
||||
|
||||
pub type KEMKeyDigests = HashMap<HashFunction, Vec<u8>>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive, EnumIter)]
|
||||
#[repr(u8)]
|
||||
pub enum HashFunction {
|
||||
Blake3 = 0,
|
||||
SHAKE256 = 1,
|
||||
SHAKE128 = 2,
|
||||
SHA256 = 3,
|
||||
}
|
||||
|
||||
impl HashFunction {
|
||||
#[cfg(feature = "digests")]
|
||||
pub fn digest<M: AsRef<[u8]>>(&self, data: M, output_length: usize) -> Vec<u8> {
|
||||
let mut out = vec![0u8; output_length];
|
||||
match self {
|
||||
HashFunction::Blake3 => {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
hasher.update(data.as_ref());
|
||||
hasher.finalize_xof().fill(&mut out);
|
||||
hasher.reset();
|
||||
}
|
||||
HashFunction::SHAKE256 => libcrux_sha3::shake256_ema(&mut out, data.as_ref()),
|
||||
HashFunction::SHAKE128 => libcrux_sha3::shake128_ema(&mut out, data.as_ref()),
|
||||
HashFunction::SHA256 => libcrux_sha3::sha256_ema(&mut out, data.as_ref()),
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HashFunction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
HashFunction::Blake3 => "blake3",
|
||||
HashFunction::SHAKE128 => "shake128",
|
||||
HashFunction::SHAKE256 => "shake256",
|
||||
HashFunction::SHA256 => "sha256",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum HashLength {
|
||||
Default = 0,
|
||||
#[num_enum(catch_all)]
|
||||
Custom(u8),
|
||||
}
|
||||
|
||||
impl Display for HashLength {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
HashLength::Default => DEFAULT_HASH_LEN.fmt(f),
|
||||
HashLength::Custom(custom_len) => custom_len.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashLength {
|
||||
pub fn decode(raw: u8) -> Result<Self, KKTCiphersuiteError> {
|
||||
// check if we're using encoding for 'default' value
|
||||
if raw == u8::from(Self::Default) {
|
||||
return Ok(Self::Default);
|
||||
}
|
||||
// otherwise, we treat it as a custom length, and we have to validate its security
|
||||
let custom_len = raw;
|
||||
|
||||
if custom_len < MINIMUM_SECURE_HASH_LEN {
|
||||
return Err(KKTCiphersuiteError::InsecureHashLen {
|
||||
requested: custom_len,
|
||||
minimum: MINIMUM_SECURE_HASH_LEN,
|
||||
});
|
||||
}
|
||||
Ok(Self::Custom(custom_len))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Option<u8>> for HashLength {
|
||||
type Error = KKTCiphersuiteError;
|
||||
|
||||
fn try_from(value: Option<u8>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
None => Ok(Self::Default),
|
||||
Some(custom_len) => Self::decode(custom_len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HashLength {
|
||||
pub const fn value(&self) -> usize {
|
||||
match self {
|
||||
HashLength::Default => DEFAULT_HASH_LEN,
|
||||
HashLength::Custom(custom_len) => *custom_len as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum SignatureScheme {
|
||||
Ed25519 = 0,
|
||||
}
|
||||
|
||||
impl SignatureScheme {
|
||||
pub const fn signing_key_length(&self) -> usize {
|
||||
match self {
|
||||
// 32 bytes
|
||||
SignatureScheme::Ed25519 => ed25519::SECRET_KEY_LENGTH,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn verification_key_length(&self) -> usize {
|
||||
match self {
|
||||
// 32 bytes
|
||||
SignatureScheme::Ed25519 => ed25519::PUBLIC_KEY_LENGTH,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn signature_length(&self) -> usize {
|
||||
match self {
|
||||
// 64 bytes
|
||||
SignatureScheme::Ed25519 => ed25519::SIGNATURE_LENGTH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SignatureScheme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
SignatureScheme::Ed25519 => "ed25519",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum KEM {
|
||||
XWing = 0,
|
||||
MlKem768 = 1,
|
||||
McEliece = 2,
|
||||
X25519 = 255,
|
||||
}
|
||||
|
||||
impl KEM {
|
||||
pub fn encapsulation_key_length(&self) -> usize {
|
||||
match self {
|
||||
KEM::MlKem768 => ml_kem768::PUBLIC_KEY_LENGTH,
|
||||
KEM::XWing => xwing::PUBLIC_KEY_LENGTH,
|
||||
KEM::X25519 => x25519::PUBLIC_KEY_LENGTH,
|
||||
KEM::McEliece => mceliece::PUBLIC_KEY_LENGTH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KEM {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
KEM::MlKem768 => "mlkem768",
|
||||
KEM::XWing => "xwing",
|
||||
KEM::X25519 => "x25519",
|
||||
KEM::McEliece => "mceliece",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct Ciphersuite {
|
||||
hash_function: HashFunction,
|
||||
signature_scheme: SignatureScheme,
|
||||
kem: KEM,
|
||||
hash_length: HashLength,
|
||||
encapsulation_key_length: usize,
|
||||
signing_key_length: usize,
|
||||
verification_key_length: usize,
|
||||
signature_length: usize,
|
||||
}
|
||||
|
||||
impl Ciphersuite {
|
||||
pub fn new(
|
||||
kem: KEM,
|
||||
hash_function: HashFunction,
|
||||
signature_scheme: SignatureScheme,
|
||||
hash_length: HashLength,
|
||||
) -> Self {
|
||||
Self {
|
||||
hash_function,
|
||||
signature_scheme,
|
||||
kem,
|
||||
hash_length,
|
||||
encapsulation_key_length: kem.encapsulation_key_length(),
|
||||
signing_key_length: signature_scheme.signing_key_length(),
|
||||
verification_key_length: signature_scheme.verification_key_length(),
|
||||
signature_length: signature_scheme.signature_length(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kem_key_len(&self) -> usize {
|
||||
self.encapsulation_key_length
|
||||
}
|
||||
|
||||
pub fn signature_len(&self) -> usize {
|
||||
self.signature_length
|
||||
}
|
||||
|
||||
pub fn signing_key_len(&self) -> usize {
|
||||
self.signing_key_length
|
||||
}
|
||||
|
||||
pub fn verification_key_len(&self) -> usize {
|
||||
self.verification_key_length
|
||||
}
|
||||
|
||||
pub fn hash_function(&self) -> HashFunction {
|
||||
self.hash_function
|
||||
}
|
||||
|
||||
pub fn kem(&self) -> KEM {
|
||||
self.kem
|
||||
}
|
||||
|
||||
pub fn signature_scheme(&self) -> SignatureScheme {
|
||||
self.signature_scheme
|
||||
}
|
||||
|
||||
pub fn hash_len(&self) -> usize {
|
||||
self.hash_length.value()
|
||||
}
|
||||
|
||||
pub fn resolve_ciphersuite(
|
||||
kem: KEM,
|
||||
hash_function: HashFunction,
|
||||
signature_scheme: SignatureScheme,
|
||||
// This should be None 99.9999% of the time
|
||||
custom_hash_length: Option<u8>,
|
||||
) -> Result<Self, KKTCiphersuiteError> {
|
||||
let hash_length = HashLength::try_from(custom_hash_length)?;
|
||||
|
||||
Ok(Ciphersuite::new(
|
||||
kem,
|
||||
hash_function,
|
||||
signature_scheme,
|
||||
hash_length,
|
||||
))
|
||||
}
|
||||
pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] {
|
||||
// [kem, hash, hashlen, sig]
|
||||
[
|
||||
self.kem.into(),
|
||||
self.hash_function.into(),
|
||||
self.hash_length.into(),
|
||||
self.signature_scheme.into(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result<Self, KKTCiphersuiteError> {
|
||||
let raw_kem = encoding[0];
|
||||
let raw_hash_function = encoding[1];
|
||||
let hash_len = encoding[2];
|
||||
let raw_signature_scheme = encoding[3];
|
||||
|
||||
let kem = KEM::try_from(raw_kem)
|
||||
.map_err(|_| KKTCiphersuiteError::UnknownKEMType { raw: raw_kem })?;
|
||||
let hash_function = HashFunction::try_from(raw_hash_function).map_err(|_| {
|
||||
KKTCiphersuiteError::UnknownHashFunctionType {
|
||||
raw: raw_hash_function,
|
||||
}
|
||||
})?;
|
||||
let hash_length = HashLength::decode(hash_len)?;
|
||||
let signature_scheme = SignatureScheme::try_from(raw_signature_scheme).map_err(|_| {
|
||||
KKTCiphersuiteError::UnknownSignatureSchemeType {
|
||||
raw: raw_signature_scheme,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self::new(kem, hash_function, signature_scheme, hash_length))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ciphersuite {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(
|
||||
&format!(
|
||||
"{}_{}({})_{}",
|
||||
self.kem, self.hash_function, self.hash_length, self.signature_scheme
|
||||
)
|
||||
.to_ascii_lowercase(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,14 @@ publish = false
|
||||
blake3 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
num_enum = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
|
||||
# internal
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] }
|
||||
nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite", features = ["digests"] }
|
||||
|
||||
libcrux-kem = { git = "https://github.com/cryspen/libcrux" }
|
||||
libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" }
|
||||
libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"] }
|
||||
libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" }
|
||||
|
||||
|
||||
@@ -1,37 +1,10 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use libcrux_kem::{Algorithm, MlKem768PublicKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::error::KKTError;
|
||||
use libcrux_kem::Algorithm;
|
||||
|
||||
pub const DEFAULT_HASH_LEN: usize = 32;
|
||||
const _: () = assert!(DEFAULT_HASH_LEN <= u8::MAX as usize);
|
||||
|
||||
pub const CIPHERSUITE_ENCODING_LEN: usize = 4;
|
||||
|
||||
pub const CURVE25519_KEY_LEN: usize = 32;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum HashFunction {
|
||||
Blake3,
|
||||
SHAKE128,
|
||||
SHAKE256,
|
||||
SHA256,
|
||||
}
|
||||
|
||||
impl Display for HashFunction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
HashFunction::Blake3 => "Blake3",
|
||||
HashFunction::SHAKE128 => "SHAKE128",
|
||||
HashFunction::SHAKE256 => "SHAKE256",
|
||||
HashFunction::SHA256 => "SHA256",
|
||||
})
|
||||
}
|
||||
}
|
||||
pub use nym_kkt_ciphersuite::*;
|
||||
|
||||
pub enum EncapsulationKey<'a> {
|
||||
MlKem768(libcrux_kem::PublicKey),
|
||||
@@ -89,200 +62,6 @@ impl<'a> EncapsulationKey<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SignatureScheme {
|
||||
Ed25519,
|
||||
}
|
||||
impl Display for SignatureScheme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
SignatureScheme::Ed25519 => "Ed25519",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum KEM {
|
||||
MlKem768,
|
||||
XWing,
|
||||
X25519,
|
||||
McEliece,
|
||||
}
|
||||
|
||||
impl Display for KEM {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
KEM::MlKem768 => "MlKem768",
|
||||
KEM::XWing => "XWing",
|
||||
KEM::X25519 => "x25519",
|
||||
KEM::McEliece => "McEliece",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Ciphersuite {
|
||||
hash_function: HashFunction,
|
||||
signature_scheme: SignatureScheme,
|
||||
kem: KEM,
|
||||
hash_length: u8,
|
||||
encapsulation_key_length: usize,
|
||||
signing_key_length: usize,
|
||||
verification_key_length: usize,
|
||||
signature_length: usize,
|
||||
}
|
||||
|
||||
impl Ciphersuite {
|
||||
pub fn kem_key_len(&self) -> usize {
|
||||
self.encapsulation_key_length
|
||||
}
|
||||
|
||||
pub fn signature_len(&self) -> usize {
|
||||
self.signature_length
|
||||
}
|
||||
pub fn signing_key_len(&self) -> usize {
|
||||
self.signing_key_length
|
||||
}
|
||||
pub fn verification_key_len(&self) -> usize {
|
||||
self.verification_key_length
|
||||
}
|
||||
pub fn hash_function(&self) -> HashFunction {
|
||||
self.hash_function
|
||||
}
|
||||
pub fn kem(&self) -> KEM {
|
||||
self.kem
|
||||
}
|
||||
pub fn signature_scheme(&self) -> SignatureScheme {
|
||||
self.signature_scheme
|
||||
}
|
||||
pub fn hash_len(&self) -> usize {
|
||||
self.hash_length as usize
|
||||
}
|
||||
|
||||
pub fn resolve_ciphersuite(
|
||||
kem: KEM,
|
||||
hash_function: HashFunction,
|
||||
signature_scheme: SignatureScheme,
|
||||
// This should be None 99.9999% of the time
|
||||
custom_hash_length: Option<u8>,
|
||||
) -> Result<Self, KKTError> {
|
||||
let hash_len = match custom_hash_length {
|
||||
Some(l) => {
|
||||
if l < 16 {
|
||||
return Err(KKTError::InsecureHashLen);
|
||||
} else {
|
||||
l
|
||||
}
|
||||
}
|
||||
None => DEFAULT_HASH_LEN as u8,
|
||||
};
|
||||
Ok(Self {
|
||||
hash_function,
|
||||
signature_scheme,
|
||||
kem,
|
||||
hash_length: hash_len,
|
||||
encapsulation_key_length: match kem {
|
||||
// 1184 bytes
|
||||
KEM::MlKem768 => MlKem768PublicKey::len(),
|
||||
// 1216 bytes = 1184 + 32
|
||||
KEM::XWing => MlKem768PublicKey::len() + CURVE25519_KEY_LEN,
|
||||
// 32 bytes
|
||||
KEM::X25519 => CURVE25519_KEY_LEN,
|
||||
// 524160 bytes
|
||||
KEM::McEliece => classic_mceliece_rust::CRYPTO_PUBLICKEYBYTES,
|
||||
},
|
||||
signing_key_length: match signature_scheme {
|
||||
// 32 bytes
|
||||
SignatureScheme::Ed25519 => ed25519::SECRET_KEY_LENGTH,
|
||||
},
|
||||
verification_key_length: match signature_scheme {
|
||||
// 32 bytes
|
||||
SignatureScheme::Ed25519 => ed25519::PUBLIC_KEY_LENGTH,
|
||||
},
|
||||
signature_length: match signature_scheme {
|
||||
// 64 bytes
|
||||
SignatureScheme::Ed25519 => ed25519::SIGNATURE_LENGTH,
|
||||
},
|
||||
})
|
||||
}
|
||||
pub fn encode(&self) -> [u8; CIPHERSUITE_ENCODING_LEN] {
|
||||
// [kem, hash, hashlen, sig]
|
||||
[
|
||||
match self.kem {
|
||||
KEM::XWing => 0,
|
||||
KEM::MlKem768 => 1,
|
||||
KEM::McEliece => 2,
|
||||
KEM::X25519 => 255,
|
||||
},
|
||||
match self.hash_function {
|
||||
HashFunction::Blake3 => 0,
|
||||
HashFunction::SHAKE256 => 1,
|
||||
HashFunction::SHAKE128 => 2,
|
||||
HashFunction::SHA256 => 3,
|
||||
},
|
||||
match self.hash_length as usize {
|
||||
DEFAULT_HASH_LEN => 0u8,
|
||||
_ => self.hash_length,
|
||||
},
|
||||
match self.signature_scheme {
|
||||
SignatureScheme::Ed25519 => 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
pub fn decode(encoding: [u8; CIPHERSUITE_ENCODING_LEN]) -> Result<Self, KKTError> {
|
||||
let kem = match encoding[0] {
|
||||
0 => KEM::XWing,
|
||||
1 => KEM::MlKem768,
|
||||
2 => KEM::McEliece,
|
||||
255 => KEM::X25519,
|
||||
_ => {
|
||||
return Err(KKTError::CiphersuiteDecodingError {
|
||||
info: format!("Undefined KEM: {}", encoding[0]),
|
||||
});
|
||||
}
|
||||
};
|
||||
let hash_function = match encoding[1] {
|
||||
0 => HashFunction::Blake3,
|
||||
1 => HashFunction::SHAKE256,
|
||||
2 => HashFunction::SHAKE128,
|
||||
3 => HashFunction::SHA256,
|
||||
_ => {
|
||||
return Err(KKTError::CiphersuiteDecodingError {
|
||||
info: format!("Undefined Hash Function: {}", encoding[1]),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let custom_hash_length = match encoding[2] {
|
||||
0 => None,
|
||||
_ => Some(encoding[2]),
|
||||
};
|
||||
|
||||
let signature_scheme = match encoding[3] {
|
||||
0 => SignatureScheme::Ed25519,
|
||||
_ => {
|
||||
return Err(KKTError::CiphersuiteDecodingError {
|
||||
info: format!("Undefined Signature Scheme: {}", encoding[3]),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Self::resolve_ciphersuite(kem, hash_function, signature_scheme, custom_hash_length)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ciphersuite {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(
|
||||
&format!(
|
||||
"{}_{}({})_{}",
|
||||
self.kem, self.hash_function, self.hash_length, self.signature_scheme
|
||||
)
|
||||
.to_ascii_lowercase(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn map_kem_to_libcrux_kem(kem: KEM) -> Result<Algorithm, KKTError> {
|
||||
match kem {
|
||||
KEM::MlKem768 => Ok(Algorithm::MlKem768),
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_S
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub const KKT_CONTEXT_LEN: usize = 7;
|
||||
pub const KKT_CONTEXT_LEN: usize = 3 + CIPHERSUITE_ENCODING_LEN;
|
||||
|
||||
// bitmask used: 0b1110_0000
|
||||
#[derive(Clone, Copy, PartialEq, Debug, IntoPrimitive, TryFromPrimitive)]
|
||||
@@ -202,13 +202,11 @@ impl KKTContext {
|
||||
info: format!("Header - Invalid KKT Mode: {raw_kkt_mode}"),
|
||||
})?;
|
||||
|
||||
let ciphersuite_bytes = header_bytes[2..6].try_into().map_err(|_| {
|
||||
KKTError::CiphersuiteDecodingError {
|
||||
info: format!(
|
||||
"Incorrect Encoding Length: actual: 4 != expected: {CIPHERSUITE_ENCODING_LEN}",
|
||||
),
|
||||
}
|
||||
})?;
|
||||
// SAFETY: we're taking exactly `CIPHERSUITE_ENCODING_LEN` bytes
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ciphersuite_bytes = header_bytes[2..2 + CIPHERSUITE_ENCODING_LEN]
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
Ok(KKTContext {
|
||||
version: kkt_version,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2025-2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
KKT_INITIAL_FRAME_AAD, ciphersuite::CURVE25519_KEY_LEN, context::KKTContext, error::KKTError,
|
||||
frame::KKTFrame,
|
||||
};
|
||||
use crate::{KKT_INITIAL_FRAME_AAD, context::KKTContext, error::KKTError, frame::KKTFrame};
|
||||
use blake3::Hasher;
|
||||
use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN};
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
@@ -36,7 +33,7 @@ impl KKTSessionSecret {
|
||||
|
||||
fn try_derive(private_key: &x25519::PrivateKey, public_key: &[u8]) -> Result<Self, KKTError> {
|
||||
let mut pub_key: [u8; 32] = [0u8; 32];
|
||||
pub_key.copy_from_slice(&public_key[0..CURVE25519_KEY_LEN]);
|
||||
pub_key.copy_from_slice(&public_key[0..x25519::PUBLIC_KEY_SIZE]);
|
||||
|
||||
// Todo: check validity of pk...
|
||||
let pk = x25519::PublicKey::from(pub_key);
|
||||
@@ -71,7 +68,7 @@ where
|
||||
let mut encrypted_frame =
|
||||
encrypt_kkt_frame(rng, &session_secret_key, kkt_frame, KKT_INITIAL_FRAME_AAD)?;
|
||||
|
||||
let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + CURVE25519_KEY_LEN);
|
||||
let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + x25519::PUBLIC_KEY_SIZE);
|
||||
output_buffer.extend_from_slice(ephemeral_public_key.as_bytes());
|
||||
output_buffer.append(&mut encrypted_frame);
|
||||
|
||||
@@ -84,19 +81,19 @@ pub fn decrypt_initial_kkt_frame(
|
||||
responder_private_key: &x25519::PrivateKey,
|
||||
encrypted_frame_bytes: &[u8],
|
||||
) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> {
|
||||
if encrypted_frame_bytes.len() < CURVE25519_KEY_LEN + TAG_LEN + NONCE_LEN {
|
||||
if encrypted_frame_bytes.len() < x25519::PUBLIC_KEY_SIZE + TAG_LEN + NONCE_LEN {
|
||||
Err(KKTError::AEADError {
|
||||
info: "Encrypted KKT Frame is too short.",
|
||||
})
|
||||
} else {
|
||||
let shared_secret = KKTSessionSecret::try_derive(
|
||||
responder_private_key,
|
||||
&encrypted_frame_bytes[0..CURVE25519_KEY_LEN],
|
||||
&encrypted_frame_bytes[0..x25519::PUBLIC_KEY_SIZE],
|
||||
)?;
|
||||
|
||||
let (kkt_frame, kkt_context) = decrypt_kkt_frame(
|
||||
&shared_secret,
|
||||
&encrypted_frame_bytes[CURVE25519_KEY_LEN..],
|
||||
&encrypted_frame_bytes[x25519::PUBLIC_KEY_SIZE..],
|
||||
KKT_INITIAL_FRAME_AAD,
|
||||
)?;
|
||||
Ok((shared_secret, kkt_frame, kkt_context))
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::KKTStatus;
|
||||
use nym_kkt_ciphersuite::error::KKTCiphersuiteError;
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::context::KKTStatus;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum KKTError {
|
||||
#[error("Signature constructor error")]
|
||||
SigConstructorError,
|
||||
#[error("Signature verification error")]
|
||||
SigVerifError,
|
||||
#[error("Ciphersuite Decoding Error: {}", info)]
|
||||
CiphersuiteDecodingError { info: String },
|
||||
#[error(transparent)]
|
||||
CiphersuiteDecodingError(#[from] KKTCiphersuiteError),
|
||||
|
||||
#[error("KEM mapping failure: {}", info)]
|
||||
KEMMapping { info: &'static str },
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::ciphersuite::HashFunction;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use classic_mceliece_rust::keypair_boxed;
|
||||
|
||||
use libcrux_sha3;
|
||||
use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KEMKeyDigests};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
pub fn generate_keypair_ed25519<R>(
|
||||
@@ -72,26 +73,18 @@ pub fn hash_key_bytes(
|
||||
hash_length: usize,
|
||||
key_bytes: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let mut hashed_key: Vec<u8> = vec![0u8; hash_length];
|
||||
match hash_function {
|
||||
HashFunction::Blake3 => {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
hasher.update(key_bytes);
|
||||
hasher.finalize_xof().fill(&mut hashed_key);
|
||||
hasher.reset();
|
||||
}
|
||||
HashFunction::SHAKE256 => {
|
||||
libcrux_sha3::shake256_ema(&mut hashed_key, key_bytes);
|
||||
}
|
||||
HashFunction::SHAKE128 => {
|
||||
libcrux_sha3::shake128_ema(&mut hashed_key, key_bytes);
|
||||
}
|
||||
HashFunction::SHA256 => {
|
||||
libcrux_sha3::sha256_ema(&mut hashed_key, key_bytes);
|
||||
}
|
||||
}
|
||||
hash_function.digest(key_bytes, hash_length)
|
||||
}
|
||||
|
||||
hashed_key
|
||||
/// attempt to produce digests of the provided key using all known [HashFunction] with a default
|
||||
/// hash length where variable output is available
|
||||
pub fn produce_key_digests(key_bytes: &[u8]) -> KEMKeyDigests {
|
||||
use strum::IntoEnumIterator;
|
||||
let mut digests = HashMap::new();
|
||||
for hash in HashFunction::iter() {
|
||||
digests.insert(hash, hash.digest(key_bytes, DEFAULT_HASH_LEN));
|
||||
}
|
||||
digests
|
||||
}
|
||||
|
||||
/// This does NOT run in constant time.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::{noise_protocol::NoiseError, replay::ReplayError};
|
||||
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
|
||||
use nym_kkt::ciphersuite::{HashFunction, KEM};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -93,4 +94,12 @@ pub enum LpError {
|
||||
|
||||
#[error("attempted to create an LP responder without providing a valid KEM key")]
|
||||
ResponderWithMissingKEMKey,
|
||||
|
||||
#[error(
|
||||
"there are no known digests for remote's KEM key with {kem} KEM and {hash_function} hash function"
|
||||
)]
|
||||
NoKnownKEMKeyDigests {
|
||||
kem: KEM,
|
||||
hash_function: HashFunction,
|
||||
},
|
||||
}
|
||||
|
||||
+20
-13
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_kkt::ciphersuite::{HashFunction, KEM};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Representation of a local Lewes Protocol peer
|
||||
@@ -44,18 +46,21 @@ 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_digest = match &self.kem_psq {
|
||||
None => Vec::new(),
|
||||
Some(kem_keys) => nym_kkt::key_utils::hash_key_bytes(
|
||||
&nym_kkt::ciphersuite::HashFunction::Blake3,
|
||||
nym_kkt::ciphersuite::DEFAULT_HASH_LEN,
|
||||
kem_keys.public_key().as_bytes(),
|
||||
),
|
||||
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 digests = HashMap::new();
|
||||
digests.insert(KEM::X25519, hashes);
|
||||
digests
|
||||
}
|
||||
};
|
||||
LpRemotePeer {
|
||||
ed25519_public: *self.ed25519.public_key(),
|
||||
x25519_public: *self.x25519.public_key(),
|
||||
expected_kem_key_digest,
|
||||
expected_kem_key_digests,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +86,7 @@ pub struct LpRemotePeer {
|
||||
pub(crate) x25519_public: x25519::PublicKey,
|
||||
|
||||
/// Expected digest of the remote's KEM key
|
||||
// TODO: this might have to be replaced by a HashMap<HashFunction, Vec<u8>> instead
|
||||
pub(crate) expected_kem_key_digest: Vec<u8>,
|
||||
pub(crate) expected_kem_key_digests: HashMap<KEM, HashMap<HashFunction, Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl LpRemotePeer {
|
||||
@@ -90,7 +94,7 @@ impl LpRemotePeer {
|
||||
LpRemotePeer {
|
||||
ed25519_public,
|
||||
x25519_public,
|
||||
expected_kem_key_digest: vec![],
|
||||
expected_kem_key_digests: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +107,11 @@ impl LpRemotePeer {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_kem_key_digest(mut self, expected_kem_key_digest: Vec<u8>) -> Self {
|
||||
self.expected_kem_key_digest = expected_kem_key_digest;
|
||||
pub fn with_kem_key_digests(
|
||||
mut self,
|
||||
expected_kem_key_digests: HashMap<KEM, HashMap<HashFunction, Vec<u8>>>,
|
||||
) -> Self {
|
||||
self.expected_kem_key_digests = expected_kem_key_digests;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::{LpError, LpMessage, LpPacket};
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_kkt::KKT_RESPONSE_AAD;
|
||||
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey};
|
||||
use nym_kkt::context::KKTContext;
|
||||
use nym_kkt::encryption::{
|
||||
KKTSessionSecret, decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame,
|
||||
encrypt_kkt_frame,
|
||||
@@ -640,6 +641,23 @@ impl LpSession {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to retrieve expected KEM key hash of the remote
|
||||
/// for [KEM] key type and [HashFunction] specified by own [KKTContext]
|
||||
fn expected_kem_key_hash(&self, context: KKTContext) -> Result<&Vec<u8>, LpError> {
|
||||
let kem = context.ciphersuite().kem();
|
||||
let hash_function = context.ciphersuite().hash_function();
|
||||
|
||||
let digests = self
|
||||
.remote_peer
|
||||
.expected_kem_key_digests
|
||||
.get(&kem)
|
||||
.ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function })?;
|
||||
|
||||
digests
|
||||
.get(&hash_function)
|
||||
.ok_or(LpError::NoKnownKEMKeyDigests { kem, hash_function })
|
||||
}
|
||||
|
||||
/// Processes a KKT response from the responder.
|
||||
///
|
||||
/// Decrypts and validates the response and stores the authenticated KEM public key
|
||||
@@ -674,12 +692,13 @@ impl LpSession {
|
||||
let remote_encapsulation_key =
|
||||
match decrypt_kkt_frame(&session_secret, encrypted_response_bytes, KKT_RESPONSE_AAD) {
|
||||
Ok((response_frame, remote_context)) => {
|
||||
let expected_kem_key_digest = self.expected_kem_key_hash(context)?;
|
||||
match initiator_ingest_response(
|
||||
&mut context,
|
||||
&response_frame,
|
||||
&remote_context,
|
||||
&self.remote_peer.ed25519_public,
|
||||
&self.remote_peer.expected_kem_key_digest,
|
||||
expected_kem_key_digest,
|
||||
) {
|
||||
Ok(remote_encapsulation_key) => remote_encapsulation_key,
|
||||
Err(e) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ nym-crypto = { workspace = true }
|
||||
nym-ip-packet-requests = { workspace = true }
|
||||
nym-sphinx = { workspace = true }
|
||||
nym-wireguard-types = { workspace = true }
|
||||
nym-kkt-ciphersuite = { path = "../nym-kkt-ciphersuite" }
|
||||
|
||||
[dev-dependencies]
|
||||
bincode.workspace = true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
use nym_authenticator_requests::AuthenticatorVersion;
|
||||
@@ -15,6 +16,7 @@ mod serialisation;
|
||||
pub use lp_messages::{
|
||||
LpGatewayData, LpRegistrationRequest, LpRegistrationResponse, RegistrationMode,
|
||||
};
|
||||
use nym_kkt_ciphersuite::{KEM, KEMKeyDigests};
|
||||
pub use serialisation::BincodeError;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -35,11 +37,10 @@ pub struct GatewayData {
|
||||
pub private_ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LpData {
|
||||
pub address: SocketAddr,
|
||||
// TODO: modify it into a map once we know more about the PSQv2 structure
|
||||
pub expected_kem_key_hash: Vec<u8>,
|
||||
pub expected_kem_key_hashes: HashMap<KEM, KEMKeyDigests>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -31,6 +31,7 @@ nym-exit-policy = { workspace = true }
|
||||
nym-noise-keys = { workspace = true }
|
||||
nym-wireguard-types = { workspace = true }
|
||||
nym-upgrade-mode-check = { workspace = true, features = ["openapi"] }
|
||||
nym-kkt-ciphersuite = { path = "../../common/nym-kkt-ciphersuite" }
|
||||
|
||||
# feature-specific dependencies:
|
||||
|
||||
|
||||
@@ -72,6 +72,28 @@ pub enum LPKEM {
|
||||
McEliece,
|
||||
}
|
||||
|
||||
impl From<LPKEM> for nym_kkt_ciphersuite::KEM {
|
||||
fn from(lpkem: LPKEM) -> Self {
|
||||
match lpkem {
|
||||
LPKEM::MlKem768 => nym_kkt_ciphersuite::KEM::MlKem768,
|
||||
LPKEM::XWing => nym_kkt_ciphersuite::KEM::XWing,
|
||||
LPKEM::X25519 => nym_kkt_ciphersuite::KEM::X25519,
|
||||
LPKEM::McEliece => nym_kkt_ciphersuite::KEM::McEliece,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_kkt_ciphersuite::KEM> for LPKEM {
|
||||
fn from(kem: nym_kkt_ciphersuite::KEM) -> Self {
|
||||
match kem {
|
||||
nym_kkt_ciphersuite::KEM::MlKem768 => LPKEM::MlKem768,
|
||||
nym_kkt_ciphersuite::KEM::XWing => LPKEM::XWing,
|
||||
nym_kkt_ciphersuite::KEM::X25519 => LPKEM::X25519,
|
||||
nym_kkt_ciphersuite::KEM::McEliece => LPKEM::McEliece,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
@@ -96,6 +118,28 @@ pub enum LPHashFunction {
|
||||
Sha256,
|
||||
}
|
||||
|
||||
impl From<LPHashFunction> for nym_kkt_ciphersuite::HashFunction {
|
||||
fn from(lp_hash_fnction: LPHashFunction) -> Self {
|
||||
match lp_hash_fnction {
|
||||
LPHashFunction::Blake3 => nym_kkt_ciphersuite::HashFunction::Blake3,
|
||||
LPHashFunction::Shake128 => nym_kkt_ciphersuite::HashFunction::SHAKE128,
|
||||
LPHashFunction::Shake256 => nym_kkt_ciphersuite::HashFunction::SHAKE256,
|
||||
LPHashFunction::Sha256 => nym_kkt_ciphersuite::HashFunction::SHA256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_kkt_ciphersuite::HashFunction> for LPHashFunction {
|
||||
fn from(kem: nym_kkt_ciphersuite::HashFunction) -> Self {
|
||||
match kem {
|
||||
nym_kkt_ciphersuite::HashFunction::Blake3 => LPHashFunction::Blake3,
|
||||
nym_kkt_ciphersuite::HashFunction::SHAKE128 => LPHashFunction::Shake128,
|
||||
nym_kkt_ciphersuite::HashFunction::SHAKE256 => LPHashFunction::Shake256,
|
||||
nym_kkt_ciphersuite::HashFunction::SHA256 => LPHashFunction::Sha256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -42,8 +42,7 @@ use nym_bin_common::bin_info;
|
||||
use nym_credential_verification::UpgradeModeState;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender};
|
||||
use nym_kkt::ciphersuite::{DEFAULT_HASH_LEN, HashFunction};
|
||||
use nym_kkt::key_utils::hash_encapsulation_key;
|
||||
use nym_kkt::key_utils::produce_key_digests;
|
||||
use nym_mixnet_client::client::ActiveConnections;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_network_requester::{
|
||||
@@ -777,28 +776,16 @@ impl NymNode {
|
||||
|
||||
fn compute_kem_key_hashes(&self) -> (LPKEM, HashMap<LPHashFunction, Vec<u8>>) {
|
||||
let kem = LPKEM::X25519;
|
||||
let mut hashes = HashMap::new();
|
||||
|
||||
let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes();
|
||||
|
||||
hashes.insert(
|
||||
LPHashFunction::Blake3,
|
||||
hash_encapsulation_key(&HashFunction::Blake3, DEFAULT_HASH_LEN, kem_key_bytes),
|
||||
);
|
||||
hashes.insert(
|
||||
LPHashFunction::Shake128,
|
||||
hash_encapsulation_key(&HashFunction::SHAKE128, DEFAULT_HASH_LEN, kem_key_bytes),
|
||||
);
|
||||
hashes.insert(
|
||||
LPHashFunction::Shake256,
|
||||
hash_encapsulation_key(&HashFunction::SHAKE256, DEFAULT_HASH_LEN, kem_key_bytes),
|
||||
);
|
||||
hashes.insert(
|
||||
LPHashFunction::Sha256,
|
||||
hash_encapsulation_key(&HashFunction::SHA256, DEFAULT_HASH_LEN, kem_key_bytes),
|
||||
);
|
||||
// convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests`
|
||||
let digests = produce_key_digests(kem_key_bytes.as_ref())
|
||||
.into_iter()
|
||||
.map(|(f, d)| (f.into(), d))
|
||||
.collect();
|
||||
|
||||
(kem, hashes)
|
||||
(kem, digests)
|
||||
}
|
||||
|
||||
pub(crate) async fn build_http_server(&self) -> Result<NymNodeHttpServer, NymNodeError> {
|
||||
|
||||
@@ -194,10 +194,10 @@ impl RegistrationClient {
|
||||
.map_err(|_| RegistrationClientError::X25519PubkeyConversionFailure)?;
|
||||
|
||||
let entry_peer = LpRemotePeer::new(self.config.entry.node.identity, entry_x25519_public)
|
||||
.with_kem_key_digest(entry_lp_data.expected_kem_key_hash);
|
||||
.with_kem_key_digests(entry_lp_data.expected_kem_key_hashes);
|
||||
|
||||
let exit_peer = LpRemotePeer::new(self.config.exit.node.identity, exit_x25519_public)
|
||||
.with_kem_key_digest(exit_lp_data.expected_kem_key_hash);
|
||||
.with_kem_key_digests(exit_lp_data.expected_kem_key_hashes);
|
||||
|
||||
// STEP 2: Establish outer session with entry gateway
|
||||
// This creates the LP session that will be used to forward packets to exit.
|
||||
|
||||
Generated
+18
-4
@@ -4181,18 +4181,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.3"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
|
||||
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.3"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
@@ -4489,6 +4490,16 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-kkt-ciphersuite"
|
||||
version = "1.20.1"
|
||||
dependencies = [
|
||||
"num_enum",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "1.20.1"
|
||||
@@ -4535,6 +4546,8 @@ dependencies = [
|
||||
"regex",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"url",
|
||||
"utoipa",
|
||||
]
|
||||
@@ -4550,6 +4563,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-exit-policy",
|
||||
"nym-http-api-client",
|
||||
"nym-kkt-ciphersuite",
|
||||
"nym-noise-keys",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-wireguard-types",
|
||||
|
||||
@@ -28,6 +28,7 @@ url = { workspace = true }
|
||||
# Nym crates
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-crypto = { path = "../../common/crypto" }
|
||||
nym-kkt-ciphersuite = { path = "../../common/nym-kkt-ciphersuite" }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-kcp = { path = "../../common/nym-kcp" }
|
||||
nym-lp = { path = "../../common/nym-lp" }
|
||||
|
||||
@@ -120,7 +120,7 @@ impl SpeedtestClient {
|
||||
let client_ip = "0.0.0.0".parse()?;
|
||||
|
||||
let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?)
|
||||
.with_kem_key_digest(self.gateway.kem_key_hash.clone());
|
||||
.with_kem_key_digests(self.gateway.kem_key_hashes.clone());
|
||||
|
||||
let mut lp_client = LpRegistrationClient::<TcpStream>::new_with_default_config(
|
||||
self.identity_keypair.clone(),
|
||||
@@ -166,7 +166,7 @@ impl SpeedtestClient {
|
||||
let client_ip = "0.0.0.0".parse()?;
|
||||
|
||||
let gw_peer = LpRemotePeer::new(self.gateway.identity, self.gateway.identity.to_x25519()?)
|
||||
.with_kem_key_digest(self.gateway.kem_key_hash.clone());
|
||||
.with_kem_key_digests(self.gateway.kem_key_hashes.clone());
|
||||
|
||||
let mut lp_client = LpRegistrationClient::new_with_default_config(
|
||||
self.identity_keypair.clone(),
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use nym_api_requests::models::{LPHashFunction, LPKEM};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_kkt_ciphersuite::{KEMKeyDigests, KEM};
|
||||
use nym_sphinx_types::Node as SphinxNode;
|
||||
use nym_topology::{NymRouteProvider, NymTopology, NymTopologyMetadata};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::IteratorRandom;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use tracing::{debug, info};
|
||||
use url::Url;
|
||||
@@ -27,7 +30,7 @@ const LP_DATA_PORT: u16 = 51264;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayInfo {
|
||||
pub identity: ed25519::PublicKey,
|
||||
pub kem_key_hash: Vec<u8>,
|
||||
pub kem_key_hashes: HashMap<KEM, KEMKeyDigests>,
|
||||
|
||||
pub sphinx_key: nym_crypto::asymmetric::x25519::PublicKey,
|
||||
/// Mix host (IP:port for Sphinx mixing)
|
||||
|
||||
Reference in New Issue
Block a user