nym_noise/
lib.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use nym_noise_keys::NoiseVersion;
5use snow::error::Prerequisite;
6use snow::Error;
7use tokio::net::TcpStream;
8use tracing::{error, warn};
9
10pub mod config;
11pub mod connection;
12pub mod error;
13pub mod stream;
14
15use crate::config::NoiseConfig;
16use crate::connection::Connection;
17use crate::error::NoiseError;
18use crate::stream::NoiseStreamBuilder;
19
20const NOISE_PSK_PREFIX: &[u8] = b"NYMTECH_NOISE_dQw4w9WgXcQ";
21
22pub const LATEST_NOISE_VERSION: NoiseVersion = NoiseVersion::V1;
23
24// TODO: this should be behind some trait because presumably, depending on the version,
25// other arguments would be needed
26mod psk_gen {
27    use crate::error::NoiseError;
28    use crate::stream::Psk;
29    use crate::NOISE_PSK_PREFIX;
30    use nym_crypto::asymmetric::x25519;
31    use nym_noise_keys::NoiseVersion;
32    use sha2::{Digest, Sha256};
33
34    pub(crate) fn generate_psk(
35        responder_pub_key: x25519::PublicKey,
36        version: NoiseVersion,
37    ) -> Result<Psk, NoiseError> {
38        match version {
39            NoiseVersion::V1 => Ok(generate_psk_v1(responder_pub_key)),
40            NoiseVersion::Unknown(noise_version) => {
41                Err(NoiseError::PskGenerationFailure { noise_version })
42            }
43        }
44    }
45
46    fn generate_psk_v1(responder_pub_key: x25519::PublicKey) -> [u8; 32] {
47        let mut hasher = Sha256::new();
48        hasher.update(NOISE_PSK_PREFIX);
49        hasher.update(responder_pub_key.to_bytes());
50        hasher.finalize().into()
51    }
52}
53
54pub async fn upgrade_noise_initiator(
55    conn: TcpStream,
56    config: &NoiseConfig,
57) -> Result<Connection<TcpStream>, NoiseError> {
58    if config.unsafe_disabled {
59        warn!("Noise is disabled in the config. Not attempting any handshake");
60        return Ok(Connection::Raw(conn));
61    }
62
63    //Get init material
64    let responder_addr = conn.peer_addr().map_err(|err| {
65        error!("Unable to extract peer address from connection - {err}");
66        Error::Prereq(Prerequisite::RemotePublicKey)
67    })?;
68
69    let Some(key) = config.get_noise_key(&responder_addr) else {
70        warn!("{responder_addr} can't speak Noise yet, falling back to TCP");
71        return Ok(Connection::Raw(conn));
72    };
73
74    let handshake_version = match key.supported_version {
75        NoiseVersion::V1 => NoiseVersion::V1,
76
77        // We're talking to a more recent node, but we can't adapt. Let's try to do our best and if it fails, it fails.
78        // If that node sees we're older, it will try to adapt too.
79        NoiseVersion::Unknown(version) => {
80            warn!("{responder_addr} is announcing an v{version} version of Noise that we don't know how to parse, we will attempt to downgrade to our current highest supported version");
81            LATEST_NOISE_VERSION
82        }
83    };
84
85    NoiseStreamBuilder::new(conn)
86        .perform_initiator_handshake(config, handshake_version, key.x25519_pubkey)
87        .await
88        .map(|stream| Connection::Noise(Box::new(stream)))
89}
90pub async fn upgrade_noise_responder(
91    conn: TcpStream,
92    config: &NoiseConfig,
93) -> Result<Connection<TcpStream>, NoiseError> {
94    if config.unsafe_disabled {
95        warn!("Noise is disabled in the config. Not attempting any handshake");
96        return Ok(Connection::Raw(conn));
97    }
98
99    //Get init material
100    let initiator_addr = match conn.peer_addr() {
101        Ok(addr) => addr,
102        Err(err) => {
103            error!("Unable to extract peer address from connection - {err}");
104            return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
105        }
106    };
107
108    // if responder doesn't announce noise support, we fallback to tcp
109    if config.get_noise_support(initiator_addr.ip()).is_none() {
110        warn!("{initiator_addr} can't speak Noise yet, falling back to TCP",);
111        return Ok(Connection::Raw(conn));
112    };
113
114    NoiseStreamBuilder::new(conn)
115        .perform_responder_handshake(config)
116        .await
117        .map(|stream| Connection::Noise(Box::new(stream)))
118}