diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 57ee4e6ecbc3..6f4dc33218d2 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -177,6 +177,9 @@ pub async fn spawn( } }); + // Spawn a worker that pre-computes McEliece key pairs for PQ tunnels + KEYPAIR_RX.get_or_init(|| tokio::sync::Mutex::new(spawn_keypair_worker(BUFSIZE))); + Ok(TunnelStateMachineHandle { command_tx, shutdown_rx, diff --git a/talpid-tunnel-config-client/src/classic_mceliece.rs b/talpid-tunnel-config-client/src/classic_mceliece.rs index 7c6db29b6874..da6024f7136d 100644 --- a/talpid-tunnel-config-client/src/classic_mceliece.rs +++ b/talpid-tunnel-config-client/src/classic_mceliece.rs @@ -8,23 +8,31 @@ use tokio::sync::{mpsc, Mutex}; /// builds. const STACK_SIZE: usize = 2 * 1024 * 1024; -/// Number of McEliece key pairs to buffer +/// Number of McEliece key pairs to buffer. Note that, using the below algorithm, they take up around +/// 537 kB each. We therefore only buffer two, which is the largest useful amount, in case of multihop. const BUFSIZE: usize = 2; /// Use the smallest CME variant with NIST security level 3. This variant has significantly smaller /// keys than the larger variants, and is considered safe. pub const ALGORITHM_NAME: &str = "Classic-McEliece-460896f-round3"; -static KEYPAIR_RX: OnceLock>> = OnceLock::new(); - type KeyPair = (PublicKey<'static>, SecretKey<'static>); +static KEYPAIR_RX: OnceLock>> = OnceLock::new(); + +/// Spawn a worker that pre computes `bufsize` McEliece key pairs in a separate thread, which can be +/// fetched asynchronously using the returned channel. +/// +/// As it can take upwards of 200 ms to generate McEliece key pairs, it needs to be done before we +/// start connecting to the tunnel. fn spawn_keypair_worker(bufsize: usize) -> mpsc::Receiver { + // As one of the key pairs will be buffered by the stack of the spawned thread, we reduce the + // capacity of the channel by one let bufsize = bufsize.checked_sub(1).expect("bufsize must be at least 1"); let (tx, rx) = mpsc::channel(bufsize); // We fork off the key computation to a separate thread for two reasons: - // * The computation uses a lot of stack, and we don't want to rely on the default - // stack being large enough or having enough space left. + // * The computation uses a lot of stack, and we don't want to rely on the default stack being + // large enough or having enough space left. // * The computation takes a long time and must not block the async runtime thread. std::thread::Builder::new() .stack_size(STACK_SIZE)