rings-node 0.20.0

Rings is a structured peer-to-peer network implementation using WebRTC, Chord algorithm, and full WebAssembly (WASM) support.
Documentation
//! Encrypted onion circuit data plane.
//!
//! Security model: forward layers are wrapped from exit to entry with the selected hop session
//! public keys. Each relay decrypts exactly one ElGamal-AEAD layer and learns only the immediate
//! next hop plus an opaque inner layer. Backward frames carry a client-encrypted AEAD payload and
//! relays forward them with local return state.

mod cell;
mod codec;
mod crypto;
mod limiter;
mod protocol;
mod reducer;
mod send_outbox;
mod shell;

#[cfg(test)]
mod tests;

use bytes::Bytes;
pub use cell::OnionCellBucket;
pub use codec::OnionCircuitEvent;
pub use crypto::encode_initial_forward;
#[cfg(rings_browser)]
pub(crate) use crypto::encode_initial_forward_link;
pub use crypto::route_first_hop;
pub(crate) use crypto::send_backward;
#[cfg(rings_native)]
pub(crate) use crypto::OnionCircuitPath;
pub use protocol::OnionCircuitCapabilities;
pub use protocol::OnionCircuitProtocol;
pub use reducer::OnionCircuitEffect;
pub use reducer::OnionCircuitState;
use rings_core::dht::Did;
use rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext;
use rings_core::ecc::PublicKey;
use rings_core::message::MessageVerification;
pub(crate) use send_outbox::OnionLinkSender;
use serde::Deserialize;
use serde::Serialize;
pub use shell::OnionCircuitExitFrame;
pub use shell::OnionCircuitHandler;
pub use shell::OnionCircuitShell;

use super::OnionServiceName;
use crate::error::Result;

/// Immediate authenticated overlay link for one already sealed circuit cell.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct OnionLink {
    peer: Did,
    recipient: PublicKey<33>,
}

impl OnionLink {
    const fn new(peer: Did, recipient: PublicKey<33>) -> Self {
        Self { peer, recipient }
    }
}

/// Namespace used by route-aware onion circuit messages.
pub const ONION_CIRCUIT_NAMESPACE: &str = "onion-circuit";

/// Security mode implemented by the current circuit wire format.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OnionCircuitSecurity {
    /// Layered ElGamal-AEAD forward frames with client-encrypted backward payloads.
    LayeredAead,
}

/// Current circuit security mode.
pub const ONION_CIRCUIT_SECURITY: OnionCircuitSecurity = OnionCircuitSecurity::LayeredAead;

/// Maximum route length encoded by local clients and maximum relay hop-budget value accepted per
/// decrypted layer.
pub const MAX_ONION_CIRCUIT_HOPS: u8 = 8;

pub(super) const MAX_ONION_RELAY_CIRCUITS: usize = 1024;
pub(super) const ONION_RELAY_RETURN_TTL_MS: u128 = 120_000;
pub(super) const ONION_FORWARD_PAYLOAD_TTL_MS: u128 = 120_000;
pub(super) const ONION_FORWARD_EXPIRY_QUANTUM_MS: u128 = 30_000;
/// Maximum authenticated lifetime accepted by an exit after receipt.
///
/// Law: replay witnesses live for this same interval, so no still-valid forward layer can outlive
/// the nonce that proves its one-shot exit effect was already consumed.
pub(super) const ONION_FORWARD_MAX_VALIDITY_MS: u128 =
    ONION_FORWARD_PAYLOAD_TTL_MS + ONION_FORWARD_EXPIRY_QUANTUM_MS;
pub(super) const ONION_CRYPTO_LIMIT_WINDOW_MS: u128 = 60_000;
pub(super) const MAX_ONION_CRYPTO_OPS_PER_WINDOW: u32 = 4096;
pub(super) const MAX_ONION_CRYPTO_OPS_GLOBAL_PER_WINDOW: u32 = 8192;
pub(super) const MAX_ONION_CRYPTO_BYTES_PER_WINDOW: u64 = 256 * 1024 * 1024;
pub(super) const MAX_ONION_CRYPTO_BYTES_GLOBAL_PER_WINDOW: u64 = 512 * 1024 * 1024;
pub(super) const MAX_ONION_CRYPTO_PEERS: usize = 64;
pub(super) const ONION_AEAD_NAMESPACE: &str = "rings-node:onion-circuit:v1";

/// Opaque application payload carried over a route-aware onion circuit.
///
/// The circuit layer knows only the service label and authenticated bytes. TCP, HTTPS, or future
/// adapters own their own payload algebra outside the encrypted circuit core.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct OnionCircuitPayload {
    /// Canonical application service selected from the onion-exit registry.
    pub service: OnionServiceName,
    /// Adapter-owned payload bytes.
    pub body: Bytes,
}

impl OnionCircuitPayload {
    /// Build an opaque circuit payload for one already-validated application service.
    pub fn new(service: OnionServiceName, body: impl Into<Bytes>) -> Self {
        Self {
            service,
            body: body.into(),
        }
    }

    /// Build an opaque circuit payload from an untrusted service string.
    pub fn try_new(service: impl AsRef<str>, body: impl Into<Bytes>) -> Result<Self> {
        Ok(Self::new(OnionServiceName::parse(service)?, body))
    }

    /// Return the canonical service selected by this payload.
    pub fn service(&self) -> &str {
        self.service.as_str()
    }

    /// Return the canonical service name selected by this payload.
    pub fn service_name(&self) -> &OnionServiceName {
        &self.service
    }

    /// Return whether this payload belongs to the already canonical `service`.
    pub fn is_service(&self, service: &OnionServiceName) -> bool {
        &self.service == service
    }

    /// Return whether this payload belongs to `service` after service-name canonicalization.
    pub fn matches_service(&self, service: &str) -> bool {
        self.service.matches(service)
    }
}

/// Client-decrypted backward payload plus the exit session proof that authenticated it.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct OnionAuthenticatedPayload {
    /// Client/exit-only return id encrypted in the exit layer.
    pub return_id: OnionReturnId,
    /// Random transcript nonce signed by the exit for ciphertext and signature freshness.
    pub nonce: OnionBackwardNonce,
    /// Monotonic sequence in the exit-to-client direction for this circuit.
    pub sequence: OnionBackwardSequence,
    /// Exit session signature over the backward payload transcript.
    pub authentication: MessageVerification,
    /// Application payload signed by the exit and encrypted to the client.
    pub payload: OnionCircuitPayload,
}

/// Client/exit-only id used to authenticate backward payloads.
///
/// This id is encrypted inside the exit layer and never appears as a relay edge header. Relays may
/// rewrite [`OnionCircuitId`] while forwarding backward frames; the client adapter accepts a
/// backward payload only when this signed return id matches its pending request or stream.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct OnionReturnId([u8; 16]);

impl OnionReturnId {
    /// Build a return id from random bytes.
    pub const fn new(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    /// Generate a random return id.
    pub fn random() -> Self {
        Self(rand::random())
    }
}

/// Random nonce for one backward payload on a circuit.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct OnionBackwardNonce([u8; 16]);

impl OnionBackwardNonce {
    /// Build a nonce from random bytes.
    pub const fn new(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    /// Generate a random backward-payload nonce.
    pub fn random() -> Self {
        Self(rand::random())
    }
}

/// Monotonic exit-to-client sequence number within one circuit.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct OnionBackwardSequence(u64);

impl OnionBackwardSequence {
    /// First sequence in a circuit direction.
    pub const FIRST: Self = Self(0);

    /// Build a sequence from its wire value.
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    /// Return the wire-order value.
    pub const fn value(self) -> u64 {
        self.0
    }
}

/// Random nonce for one forward exit payload on a circuit.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct OnionForwardNonce([u8; 16]);

impl OnionForwardNonce {
    /// Build a nonce from random bytes.
    pub const fn new(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    /// Generate a random forward-payload nonce.
    pub fn random() -> Self {
        Self(rand::random())
    }
}

/// Monotonic client-to-exit sequence number within one circuit.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct OnionForwardSequence(u64);

impl OnionForwardSequence {
    /// First sequence in a circuit direction.
    pub const FIRST: Self = Self(0);

    /// Build a sequence from its wire value.
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    /// Return the wire-order value.
    pub const fn value(self) -> u64 {
        self.0
    }
}

/// Backward payload that has passed exit identity, signature, and freshness checks.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OnionVerifiedPayload {
    /// Verified client/exit return id.
    pub return_id: OnionReturnId,
    /// Authenticated transcript nonce; replay admission is carried by `sequence`.
    pub nonce: OnionBackwardNonce,
    /// Verified monotonic backward sequence.
    pub sequence: OnionBackwardSequence,
    /// Verified application payload.
    pub payload: OnionCircuitPayload,
}

/// Client return key encrypted into the exit layer.
#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct OnionClientReturn {
    /// Client session public key used for backward AEAD payloads.
    pub session_public_key: PublicKey<33>,
    /// Client/exit-only id used to authenticate backward payloads.
    pub return_id: OnionReturnId,
}

impl OnionClientReturn {
    /// Build a client return descriptor with a fresh return id.
    pub fn new(session_public_key: PublicKey<33>) -> Self {
        Self {
            session_public_key,
            return_id: OnionReturnId::random(),
        }
    }
}

/// Edge-local circuit id.
///
/// Invariant: an [`OnionCircuitId`] identifies exactly one directed edge of one route. Relay layers
/// carry the next edge id under AEAD; backward forwarding rewrites the header back to the previous
/// edge id.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct OnionCircuitId([u8; 16]);

impl OnionCircuitId {
    /// Build a circuit id from random bytes.
    pub const fn new(bytes: [u8; 16]) -> Self {
        Self(bytes)
    }

    /// Generate a random circuit id.
    pub fn random() -> Self {
        Self(rand::random())
    }
}

/// Forward direction: client -> relays -> exit.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct OnionForwardFrame {
    /// Edge-local circuit id for the receiving hop.
    pub circuit_id: OnionCircuitId,
    /// AEAD-encrypted layer for the receiving hop.
    pub layer: AeadCiphertext,
}

/// Backward direction: exit -> relays -> client.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct OnionBackwardFrame {
    /// Edge-local circuit id for the receiving relay or client.
    pub circuit_id: OnionCircuitId,
    /// AEAD payload encrypted to the client session public key.
    pub payload: AeadCiphertext,
}

/// Authenticated immediate path used to originate one backward cell at an exit.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct OnionBackwardPath {
    /// Edge-local circuit id expected by the immediate return peer.
    pub circuit_id: OnionCircuitId,
    /// Immediate overlay return peer.
    pub return_peer: Did,
    /// Session key that encrypts the hop-to-hop return cell.
    pub return_session_public_key: PublicKey<33>,
    /// Client-only key and return id for the inner signed payload.
    pub client: OnionClientReturn,
}

impl OnionBackwardPath {
    /// Build a return path from values authenticated in the decrypted exit layer.
    pub const fn new(
        circuit_id: OnionCircuitId,
        return_peer: Did,
        return_session_public_key: PublicKey<33>,
        client: OnionClientReturn,
    ) -> Self {
        Self {
            circuit_id,
            return_peer,
            return_session_public_key,
            client,
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub(super) enum OnionForwardLayer {
    Relay {
        next_hop: Did,
        next_circuit_id: OnionCircuitId,
        next_session_public_key: PublicKey<33>,
        return_session_public_key: PublicKey<33>,
        inner: AeadCiphertext,
    },
    Exit {
        client: OnionClientReturn,
        return_session_public_key: PublicKey<33>,
        expires_at_ms: u128,
        forward_nonce: OnionForwardNonce,
        forward_sequence: OnionForwardSequence,
        payload: OnionCircuitPayload,
    },
}