# sealed-channel
`sealed-channel` is a small, transport-agnostic authenticated record channel.
It derives directional ChaCha20-Poly1305 record keys from:
- a high-entropy pre-shared secret (PSK),
- an externally supplied ephemeral Diffie-Hellman shared secret,
- the exact wire bytes of the client and server handshake messages.
The crate is pure compute. It performs no networking, no I/O, and no random
number generation. Callers own the transport, the ephemeral key exchange, and
all randomness.
## What It Provides
- HKDF-SHA256 key schedule with transcript binding.
- Directional client-to-server and server-to-client AEAD keys.
- Strict in-order record opening with replay and reordering rejection.
- Deterministic nonce construction that fails closed on counter exhaustion.
- `#![no_std]` with `alloc`.
- `#![forbid(unsafe_code)]`.
## Usage
```rust
use sealed_channel::schedule;
fn main() -> Result<(), sealed_channel::Error> {
let psk = b"a-high-entropy-pre-shared-secret";
let dh_shared_secret = [7_u8; 32];
let client_hello = b"client hello wire bytes";
let server_challenge = b"server challenge wire bytes";
let client_keys = schedule::derive(
psk,
&dh_shared_secret,
client_hello,
server_challenge,
)?;
let server_keys = schedule::derive(
psk,
&dh_shared_secret,
client_hello,
server_challenge,
)?;
let (mut client_seal, _client_open) = client_keys.into_client();
let (_server_seal, mut server_open) = server_keys.into_server();
let frame = client_seal.seal(b"hello")?;
let plaintext = server_open.open(&frame)?;
assert_eq!(plaintext.as_slice(), b"hello");
Ok(())
}
```
In a real protocol, each side should use fresh ephemeral keys per connection,
derive the same 32-byte DH shared secret, and pass the exact handshake bytes
seen on the wire.
## Construction
The key schedule computes:
```text
ikm = dh_shared_secret || psk
hk = HKDF-SHA256(salt = transcript, ikm)
```
Four domain-separated labels expand to:
- client-to-server AEAD key,
- server-to-client AEAD key,
- client-to-server nonce prefix,
- server-to-client nonce prefix.
Records are encoded as:
```text
The clear `0xE0 || seq` header is authenticated as AEAD additional data. The
nonce is:
```text
nonce_prefix:4 || seq:u64be
```
The sequence number is strict and monotonic per direction. Replays,
reordering, malformed frames, and authentication failures return errors
without advancing opener state.
## Security Boundary
Authentication strength is the entropy of the PSK.
An active relay can run its own Diffie-Hellman exchange with each peer. The
PSK is therefore the secret that authenticates the channel against that active
attacker. Use a high-entropy PSK, at least 128 bits and preferably 256 bits.
Do not use a PIN, passphrase, or other low-entropy human secret as the PSK.
Authenticating weak secrets against active attackers requires a PAKE such as
SPAKE2+ or OPAQUE. This crate is not a PAKE.
Forward secrecy depends on the caller using fresh ephemeral DH keys and
discarding the ephemeral private keys after the handshake.
## Non-Goals
- No transport security or metadata confidentiality.
- No TLS replacement.
- No PAKE.
- No randomness.
- No handshake format.
- No network protocol.
## Status
This is a pre-1.0 crate and has not had an external security audit. Treat the
API and wire format as subject to change until the crate reaches a stable
release.
## License
Licensed under either of Apache-2.0 or MIT at your option.