sealed-channel 0.1.0

Transport-agnostic, forward-secret authenticated record channel from a PSK plus an externally-supplied ephemeral DH shared secret (ChaCha20-Poly1305 + HKDF-SHA256).
Documentation
//! Error type for the sealed channel.
//!
//! The variants are intentionally *detailed* so that internal logic and tests
//! can distinguish failure modes precisely. However, the **consumer** of this
//! crate (not this crate itself) is responsible for collapsing any error into a
//! single, generic reason before it is exposed on a wire or to a remote peer.
//! Leaking the precise failure reason (e.g. distinguishing an out-of-order
//! frame from an authentication failure) to an attacker can create oracles, so
//! callers should map every [`Error`] to one opaque "channel error" externally.

use core::fmt;

/// Errors produced by the sealed channel.
///
/// This crate never panics on attacker-controlled input; every fallible
/// operation returns one of these variants instead.
///
/// Note: this type intentionally does **not** implement `std::error::Error`
/// because the crate is `#![no_std]`.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
    /// The supplied Diffie-Hellman shared secret is all-zero.
    ///
    /// X25519 produces an all-zero output for low-order points (RFC 7748),
    /// which would make the resulting keys predictable; such a secret is
    /// rejected with a constant-time check.
    WeakSharedSecret,
    /// The HKDF key-derivation step failed (e.g. an `expand` length error).
    KeyDerivation,
    /// AEAD decryption (authentication) failed: the frame was tampered with,
    /// the keys do not match, or the nonce/AAD did not correspond.
    Decrypt,
    /// A frame arrived with a sequence number other than the next expected
    /// one (replay or reordering).
    OutOfOrder,
    /// The 64-bit sequence counter is exhausted. The channel fails closed
    /// rather than wrapping or reusing a nonce.
    SequenceExhausted,
    /// A frame was too short, or did not begin with the expected magic byte.
    MalformedFrame,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let msg = match self {
            Error::WeakSharedSecret => "weak (all-zero) Diffie-Hellman shared secret",
            Error::KeyDerivation => "key derivation failed",
            Error::Decrypt => "AEAD authentication/decryption failed",
            Error::OutOfOrder => "frame sequence number out of order",
            Error::SequenceExhausted => "sequence counter exhausted",
            Error::MalformedFrame => "malformed frame",
        };
        f.write_str(msg)
    }
}