Dependency Hell with `kyberlib`

I'm trying to use the crate kyberlib to implement quantum-resistant key exchange (for a school project). Unfortunately, cargo doesn't like something about the traits implemented by my CSPRNG (ChaCha20):

the trait bound `ChaCha20Rng: kyberlib::RngCore` is not satisfied
two types coming from two different versions of the same crate are different types even if they look the same
you can use `cargo tree` to explore your dependency treerustcClick for full compiler diagnostic
qsh_kyberlib.rs(41, 26): required by a bound introduced by this call
lib.rs(142, 1): there are multiple different versions of crate `rand_core` in the dependency graph
api.rs(37, 8): required by a bound in `kyberlib::keypair`
the trait bound `ChaCha20Rng: kyberlib::CryptoRng` is not satisfied
two types coming from two different versions of the same crate are different types even if they look the same
you can use `cargo tree` to explore your dependency treerustcClick for full compiler diagnostic
qsh_kyberlib.rs(41, 26): required by a bound introduced by this call
lib.rs(209, 1): there are multiple different versions of crate `rand_core` in the dependency graph
api.rs(37, 18): required by a bound in `kyberlib::keypair`

I have little idea what's going on, and I need this solved ASAP.
It seems that the maintainer wrote his own RandCore and CryptoRng..?

Code:

/*!
	Implements the default key exchange method, kyberlib (CRYSTALS-Kyber).
	Key exchange method 0.
*/

use std::array::TryFromSliceError;

// External dependancies go here:
use rand_chacha::ChaCha20Rng;
use rand_chacha::rand_core::SeedableRng;
use kyberlib::{keypair, Ake, AkeSendInit, AkeSendResponse, Keypair, KyberLibError, PublicKey};

// Internal dependancies go here:
use super::KeyExchanger;


pub struct KyberlibKeyExchanger {

	// Generator for random numbers:
	random: ChaCha20Rng,

	// Stores the state for the key exchange:
	state: Ake,

	// Stores the keypair for this exchange:
	keypair: Keypair,

	// Stores the public key of the remote host:
	remote_pubkey: Option<PublicKey>,

}

impl KeyExchanger for KyberlibKeyExchanger {
	type Error = KyberLibError;
	type ClientInit = AkeSendInit;
	type ServerInit = AkeSendResponse;

	fn new() -> Result<Self, Self::Error> {
		let mut random: ChaCha20Rng = ChaCha20Rng::from_os_rng();
		let state: Ake = Ake::new();
		let keypair: Keypair = keypair(&mut random)?;

		return Ok(Self {
			random: random,
			state: state,
			keypair: keypair,
			remote_pubkey: None,
		});
	}
	
	fn get_local_pubkey(&self) -> &[u8] {
		return &self.keypair.public;
	}

	fn set_remote_pubkey(&mut self, pubkey: &[u8]) -> Result<(), TryFromSliceError> {
		self.remote_pubkey = Some(PublicKey::from(pubkey.try_into()?));
		return Ok(());
	}

	fn client_init(&mut self) -> Result<Self::ClientInit, Self::Error> {
		// Check if there's a public key stored here yet:
		if let Some(pubkey) = self.remote_pubkey {
			// If there is, run `client_init`, propagate any errors, and return the data as an owned Vec:
			return Ok(self.state.client_init(&pubkey, &mut self.random)?);
		} else {
			// Or return this error (`MissingKey` would be more appropriate, but that doesn't exist😁):
			return Err(KyberLibError::InvalidKey);
		}
	}

	fn server_init(&mut self, client_init: Self::ClientInit) -> Result<Self::ServerInit, Self::Error> {
		// Check if there's a public key:
		if let Some(pubkey) = self.remote_pubkey {
			// If yes, `server_init`:
			return Ok(self.state.server_receive(client_init, &pubkey, &self.keypair.secret, &mut self.random)?);
		} else {
			// If not, error:
			return Err(KyberLibError::InvalidKey);
		}
	}

	fn client_confirm(&mut self, server_init: Self::ServerInit) -> Result<(), Self::Error> {
		// Final step: propagate errors:
		self.state.client_confirm(server_init, &self.keypair.secret)?;
		// Or return `Ok`:
		return Ok(());
	}

	fn shared_secret(&self) -> &[u8] {
		return &self.state.shared_secret;
	}

}

#[test]
fn test_kyberlib_key_exchanger() {
	//let key_exchanger: KyberlibKeyExchanger = KyberlibKeyExchanger::new().expect("Failed to instantiate a `KyberlibKeyExchanger`");	// I know, not really a key. But what was I supposed to do? This is testing functionality, not security...
	todo!()	// Add the rest of the test, once we have the rest of the features.
}

Please share the full code and the Cargo.toml

mod.rs:

// Module declarations go here:
#[cfg(feature = "kyberlib")]
pub mod qsh_kyberlib;

use std::array::TryFromSliceError;

// Re-export them here:
#[cfg(feature = "kyberlib")]
pub use qsh_kyberlib::KyberlibKeyExchanger;

pub trait KeyExchanger {
	type Error;
	type ClientInit;
	type ServerInit;

	fn new() -> Result<Self, Self::Error> where Self: Sized;

	/// Exports the local pubkey, so that it can be sent to the remote host.
	/// Run this when you want to start a key exchange; both parties having
	/// the other's public key is a necissary step in key exchanging.
	fn get_local_pubkey(&self) -> &[u8];
	
	/// Set a remote host public key.
	/// This is run using the output of the above function, on the other
	/// side of the connection.
	fn set_remote_pubkey(&mut self, pubkey: &[u8]) -> Result<(), TryFromSliceError>;

	/// Performs a client-side init.
	/// (note that this can also be called on the server side,
	/// client here really means "initiator")
	/// Requires that the server's public key is already here, and saved in
	/// the structure using `set_remote_pubkey()`.
	fn client_init(&mut self) -> Result<Self::ClientInit, Self::Error>;

	/// Generate the server response. Requires the client's public key, and their request for key exchange.
	fn server_init(&mut self, client_init: Self::ClientInit) -> Result<Self::ServerInit, Self::Error>;

	/// Confirm it! Requires the server's response to the request for key exchange.
	fn client_confirm(&mut self, server_init: Self::ServerInit) -> Result<(), Self::Error>;

	/// The whole point: a shared secret.
	fn shared_secret(&self) -> &[u8];

}

qsh_kyberlib.rs:

/*!
	Implements the default key exchange method, kyberlib (CRYSTALS-Kyber).
	Key exchange method 0.
*/

use std::array::TryFromSliceError;

// External dependancies go here:
use rand_chacha::ChaCha20Rng;
use rand_chacha::rand_core::SeedableRng;
use kyberlib::{keypair, Ake, AkeSendInit, AkeSendResponse, Keypair, KyberLibError, PublicKey};

// Internal dependancies go here:
use super::KeyExchanger;


pub struct KyberlibKeyExchanger {

	// Generator for random numbers:
	random: ChaCha20Rng,

	// Stores the state for the key exchange:
	state: Ake,

	// Stores the keypair for this exchange:
	keypair: Keypair,

	// Stores the public key of the remote host:
	remote_pubkey: Option<PublicKey>,

}

impl KeyExchanger for KyberlibKeyExchanger {
	type Error = KyberLibError;
	type ClientInit = AkeSendInit;
	type ServerInit = AkeSendResponse;

	fn new() -> Result<Self, Self::Error> {
		let mut random: ChaCha20Rng = ChaCha20Rng::from_os_rng();
		let state: Ake = Ake::new();
		let keypair: Keypair = keypair(&mut random)?;

		return Ok(Self {
			random: random,
			state: state,
			keypair: keypair,
			remote_pubkey: None,
		});
	}
	
	fn get_local_pubkey(&self) -> &[u8] {
		return &self.keypair.public;
	}

	fn set_remote_pubkey(&mut self, pubkey: &[u8]) -> Result<(), TryFromSliceError> {
		self.remote_pubkey = Some(PublicKey::from(pubkey.try_into()?));
		return Ok(());
	}

	fn client_init(&mut self) -> Result<Self::ClientInit, Self::Error> {
		// Check if there's a public key stored here yet:
		if let Some(pubkey) = self.remote_pubkey {
			// If there is, run `client_init`, propagate any errors, and return the data as an owned Vec:
			return Ok(self.state.client_init(&pubkey, &mut self.random)?);
		} else {
			// Or return this error (`MissingKey` would be more appropriate, but that doesn't exist😁):
			return Err(KyberLibError::InvalidKey);
		}
	}

	fn server_init(&mut self, client_init: Self::ClientInit) -> Result<Self::ServerInit, Self::Error> {
		// Check if there's a public key:
		if let Some(pubkey) = self.remote_pubkey {
			// If yes, `server_init`:
			return Ok(self.state.server_receive(client_init, &pubkey, &self.keypair.secret, &mut self.random)?);
		} else {
			// If not, error:
			return Err(KyberLibError::InvalidKey);
		}
	}

	fn client_confirm(&mut self, server_init: Self::ServerInit) -> Result<(), Self::Error> {
		// Final step: propagate errors:
		self.state.client_confirm(server_init, &self.keypair.secret)?;
		// Or return `Ok`:
		return Ok(());
	}

	fn shared_secret(&self) -> &[u8] {
		return &self.state.shared_secret;
	}

}

#[test]
fn test_kyberlib_key_exchanger() {
	// Test constructor:
	let mut alice: KyberlibKeyExchanger = KyberlibKeyExchanger::new().expect("Failed to create `alice`!");
	let mut bob: KyberlibKeyExchanger = KyberlibKeyExchanger::new().expect("Failed to create `bob`!");
	// Exchange public keys:
	bob.set_remote_pubkey(alice.get_local_pubkey()).expect("Failed to set `bob`'s pubkey!");
	alice.set_remote_pubkey(bob.get_local_pubkey()).expect("Failed to set `alice`'s pubkey!");
	// Alice is the client. Test `client_init`:
	let client_init: <KyberlibKeyExchanger as KeyExchanger>::ClientInit = alice.client_init().expect("Failed to initialize client `alice`!");
	// Bob is the server. Test `server_init`:
	let server_init: <KyberlibKeyExchanger as KeyExchanger>::ServerInit = bob.server_init(client_init).expect("Failed to initialize server `bob`!");
	// Check it:
	alice.client_confirm(server_init).expect("Failed to confirm client `alice`!");
}

Cargo.toml:

[package]
name = "qsh"
version = "0.1.0"
edition = "2024"
authors = ["Leyvi Rose <leyvirose@gmail.com>"]

[[bin]]
name = "qshd"
path = "src/qshd/main.rs"

[dependencies]
aes-gcm = { version = "0.10.3", features = ["zeroize"], optional = true }
bincode = { version = "2.0.1", features = ["std", "derive", "alloc", "bincode_derive", "serde"] }
bitflags = { version = "2.9.0", features = ["core", "serde"] }
config = { version = "0.15.9", default-features = false, features = ["toml"] }
kyberlib = { version = "0.0.6", features = ["nasm-rs"], optional = true }
lz4_flex = { version = "0.11.3", default-features = false, optional = true }
rand = { version = "0.9.1", features = ["std", "std_rng"] }
rand_chacha = { version = "0.9.0", features = ["os_rng"] }
thiserror = { version = "2.0.12", default-features = false }

[features]
default = ["lz4_flex", "kyberlib", "aes-gcm"]
lz4_flex = ["dep:lz4_flex"]
kyberlib = ["dep:kyberlib"]
aes-gcm = ["dep:aes-gcm"]

kyberlib is still using rand 0.8 while you're depending on rand 0.9. Downgrade the version of your rand (and likely also rand_chacha) to 0.8.

1 Like

I can't do that - I need ChaCha20Rng::from_os_rng(), which doesn't exist in rand_chacha@0.3.1. Do I need to manually patch kyberlib?

In this version the method is called from_entropy.

Or possibly ChaCha20Rng::from_rng(OsRng)

Thanks!
Works great.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.