An open API service indexing awesome lists of open source software.

https://github.com/juliuspleunes4/noiseframework

Noise Protocol Framework in Python.
https://github.com/juliuspleunes4/noiseframework

cryptography noise pypi python

Last synced: 5 months ago
JSON representation

Noise Protocol Framework in Python.

Awesome Lists containing this project

README

          

# NoiseFramework

[![Official Website](https://img.shields.io/badge/๐ŸŒ%20Official%20Website-www.noiseframework.com-4A90E2?style=for-the-badge&logo=globe&logoColor=white)](https://www.noiseframework.com)


[![PyPI version](https://img.shields.io/pypi/v/noiseframework.svg)](https://pypi.org/project/noiseframework/)
[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![GitHub Issues](https://img.shields.io/github/issues/juliuspleunes4/noiseframework)](https://github.com/juliuspleunes4/noiseframework/issues)
[![GitHub Stars](https://img.shields.io/github/stars/juliuspleunes4/noiseframework)](https://github.com/juliuspleunes4/noiseframework/stargazers)
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

> A professional, secure, and easy-to-use implementation of the [Noise Protocol Framework](https://noiseprotocol.org/) in Python.

**NoiseFramework** provides cryptographically sound, specification-compliant implementations of Noise handshake patterns for building secure communication channels. It is designed to be both simple to integrate into applications and robust enough for production use.

---

## ๐Ÿ“‹ Table of Contents

- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [Python API](#python-api)
- [Command-Line Interface](#command-line-interface)
- [Python API Documentation](#-python-api-documentation)
- [Basic Handshake (Noise_XX)](#basic-handshake-noise_xx)
- [Anonymous Pattern (Noise_NN)](#anonymous-pattern-noise_nn)
- [Pre-Shared Key Pattern (Noise_IK)](#pre-shared-key-pattern-noise_ik)
- [Transport Layer Encryption](#transport-layer-encryption)
- [Error Handling](#error-handling)
- [Logging](#logging)
- [Message Framing](#message-framing)
- [Async/Await Support](#asyncawait-support)
- [Pre-Shared Key (PSK) Support](#pre-shared-key-psk-support)
- [Fallback Pattern Support](#fallback-pattern-support)
- [High-Level Connection API](#high-level-connection-api)
- [CLI Documentation](#-cli-documentation)
- [Generate Keypair](#generate-keypair)
- [Validate Pattern](#validate-pattern)
- [Show Information](#show-information)
- [Supported Patterns](#-supported-patterns)
- [Cryptographic Primitives](#-cryptographic-primitives)
- [Architecture](#-architecture)
- [Testing](#-testing)
- [Performance](#-performance)
- [Contributing](#-contributing)
- [Security](#-security)
- [FAQ](#-faq)
- [License](#-license)
- [Acknowledgments](#-acknowledgments)

---

## โœจ Features

- **๐Ÿ“œ Spec-Compliant**: Implements the [Noise Protocol Framework specification](https://noiseprotocol.org/noise.html) faithfully
- **๐Ÿ”’ Secure by Default**: Uses well-vetted cryptographic primitives from trusted libraries
- **๐Ÿ Pythonic API**: Simple, type-hinted interfaces that are easy to use and hard to misuse
- **๐Ÿ› ๏ธ CLI Tool**: Command-line interface for encryption, decryption, and handshake operations
- **โœ… Well-Tested**: Comprehensive test suite with 311 tests achieving 100% pass rate
- **๐Ÿ“ฆ Zero Config**: Works out-of-the-box with sensible defaults
- **๐Ÿ”ง Flexible**: Supports multiple DH functions, cipher suites, and hash functions
- **๐Ÿ›ก๏ธ PSK Support**: Pre-Shared Key patterns for quantum-resistant authentication (psk0-psk4)
- **๐Ÿ“– Documented**: Extensive documentation with examples and best practices
- **๐Ÿ” Helpful Errors**: Custom exceptions with actionable error messages guide you to fix issues quickly
- **๐Ÿ“ Built-in Logging**: Comprehensive logging support for debugging and monitoring
- **๐Ÿ“ฆ Message Framing**: Automatic length-prefixed framing for stream-based transports
- **โšก Async/Await**: Full async support for modern Python asyncio applications
- **๐Ÿ”— High-Level Connection API**: `NoiseConnection` and `AsyncNoiseConnection` for easy-to-use connection management

---

## ๐Ÿ“ฆ Installation

### From PyPI (Recommended)

```bash
pip install noiseframework
```

### From Source

```bash
git clone https://github.com/juliuspleunes4/noiseframework.git
cd noiseframework
pip install -e .
```

### Requirements

- Python 3.8 or higher
- Dependencies are automatically installed via pip

---

## ๐Ÿš€ Quick Start

### Python API

```python
from noiseframework import NoiseHandshake, NoiseTransport

# === INITIATOR SIDE ===
initiator = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.generate_static_keypair()
initiator.initialize()

# === RESPONDER SIDE ===
responder = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.generate_static_keypair()
responder.initialize()

# === HANDSHAKE ===
msg1 = initiator.write_message(b"")
responder.read_message(msg1)

msg2 = responder.write_message(b"")
initiator.read_message(msg2)

msg3 = initiator.write_message(b"")
responder.read_message(msg3)

# === TRANSPORT ENCRYPTION ===
init_send, init_recv = initiator.to_transport()
resp_send, resp_recv = responder.to_transport()

init_transport = NoiseTransport(init_send, init_recv)
resp_transport = NoiseTransport(resp_send, resp_recv)

# Send encrypted messages
ciphertext = init_transport.send(b"Hello, secure world!")
plaintext = resp_transport.receive(ciphertext)
print(plaintext) # b"Hello, secure world!"
```

### Command-Line Interface

```bash
# Generate a keypair
noiseframework generate-keypair --dh 25519 -o mykey
# Creates: mykey_private.key, mykey_public.key

# Validate a pattern string
noiseframework validate-pattern "Noise_XX_25519_ChaChaPoly_SHA256"

# Show supported primitives
noiseframework info

# Use shorter aliases
noiseframework genkey --dh 25519 -o mykey
noiseframework validate "Noise_XX_25519_ChaChaPoly_SHA256"
```

---

## ๐Ÿ“– Python API Documentation

### Basic Handshake (Noise_XX)

The `XX` pattern provides mutual authentication with no prior knowledge required. Both parties exchange static keys during the handshake.

```python
from noiseframework import NoiseHandshake, NoiseTransport

# === INITIATOR SIDE ===
initiator = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.generate_static_keypair() # Generate static key
initiator.initialize()

# Send first message (-> e)
msg1 = initiator.write_message(b"")

# === RESPONDER SIDE ===
responder = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.generate_static_keypair() # Generate static key
responder.initialize()

# Process first message and send response (-> e, ee, s, es)
responder.read_message(msg1)
msg2 = responder.write_message(b"")

# === INITIATOR SIDE (continued) ===
# Process second message and send final (-> s, se)
initiator.read_message(msg2)
msg3 = initiator.write_message(b"")

# === RESPONDER SIDE (continued) ===
# Process final message
responder.read_message(msg3)

# === BOTH SIDES NOW HAVE SECURE CHANNEL ===
# Get transport cipher pairs
init_send, init_recv = initiator.to_transport()
resp_send, resp_recv = responder.to_transport()

# Create transport wrappers
init_transport = NoiseTransport(init_send, init_recv)
resp_transport = NoiseTransport(resp_send, resp_recv)

# Send encrypted data (initiator -> responder)
ciphertext = init_transport.send(b"Secret payload")
plaintext = resp_transport.receive(ciphertext)
assert plaintext == b"Secret payload"

# Send encrypted data (responder -> initiator)
ciphertext = resp_transport.send(b"Response data")
plaintext = init_transport.receive(ciphertext)
assert plaintext == b"Response data"
```

### Anonymous Pattern (Noise_NN)

The `NN` pattern provides encryption without authentication. No static keys are required.

```python
from noiseframework import NoiseHandshake, NoiseTransport

# === INITIATOR SIDE ===
initiator = NoiseHandshake("Noise_NN_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.initialize()

# Send first message (-> e)
msg1 = initiator.write_message(b"")

# === RESPONDER SIDE ===
responder = NoiseHandshake("Noise_NN_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.initialize()

# Process first message and send response (-> e, ee)
responder.read_message(msg1)
msg2 = responder.write_message(b"")

# === INITIATOR SIDE (continued) ===
# Process second message - handshake complete
initiator.read_message(msg2)

# === CREATE TRANSPORT ===
init_send, init_recv = initiator.to_transport()
resp_send, resp_recv = responder.to_transport()

init_transport = NoiseTransport(init_send, init_recv)
resp_transport = NoiseTransport(resp_send, resp_recv)

# Now both sides can communicate securely (but without authentication)
ciphertext = init_transport.send(b"Anonymous message")
plaintext = resp_transport.receive(ciphertext)
```

### Pre-Shared Key Pattern (Noise_IK)

The `IK` pattern allows the initiator to know the responder's static public key in advance. The initiator's identity is hidden.

```python
from noiseframework import NoiseHandshake, NoiseTransport

# === SETUP: Generate responder's static keypair ===
responder_setup = NoiseHandshake("Noise_IK_25519_ChaChaPoly_SHA256")
responder_setup.set_as_responder()
responder_setup.generate_static_keypair()
responder_private = responder_setup.static_private
responder_public = responder_setup.static_public

# === INITIATOR SIDE ===
initiator = NoiseHandshake("Noise_IK_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.generate_static_keypair() # Generate own static key
initiator.set_remote_static_public_key(responder_public) # Know responder's key
initiator.initialize()

# Send first message (-> e, es, s, ss)
msg1 = initiator.write_message(b"")

# === RESPONDER SIDE ===
responder = NoiseHandshake("Noise_IK_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.set_static_keypair(responder_private, responder_public) # Use existing keypair
responder.initialize()

# Process first message and send response (-> e, ee, se)
responder.read_message(msg1)
msg2 = responder.write_message(b"")

# === INITIATOR SIDE (continued) ===
# Process second message - handshake complete
initiator.read_message(msg2)

# === CREATE TRANSPORT ===
init_send, init_recv = initiator.to_transport()
resp_send, resp_recv = responder.to_transport()

init_transport = NoiseTransport(init_send, init_recv)
resp_transport = NoiseTransport(resp_send, resp_recv)

# Secure authenticated communication
ciphertext = init_transport.send(b"Authenticated message")
plaintext = resp_transport.receive(ciphertext)
```

### Transport Layer Encryption

After handshake completion, use the transport layer for ongoing encrypted communication:

```python
from noiseframework import NoiseTransport

# After successful handshake, get cipher states
send_cipher, recv_cipher = handshake.to_transport()

# Create transport wrapper
transport = NoiseTransport(send_cipher, recv_cipher)

# Encrypt and send data
ciphertext = transport.send(b"Sensitive data")

# Decrypt received data
plaintext = transport.receive(ciphertext)

# Send with associated data (authenticated but not encrypted)
ciphertext = transport.send(b"payload", ad=b"metadata")
plaintext = transport.receive(ciphertext, ad=b"metadata")

# Track nonces
print(f"Messages sent: {transport.get_send_nonce()}")
print(f"Messages received: {transport.get_receive_nonce()}")

# Transport automatically handles:
# - Nonce increment
# - Authentication tags
# - AEAD encryption/decryption
```

### Error Handling

NoiseFramework provides helpful custom exceptions with actionable error messages:

```python
from noiseframework import NoiseHandshake, NoiseTransport
from noiseframework.exceptions import (
NoiseError, # Base class - catches all framework errors
UnsupportedPatternError, # Invalid pattern
RoleNotSetError, # Role not set
AuthenticationError, # Decryption/authentication failure
)

# Catch specific exceptions
try:
hs = NoiseHandshake("Invalid_Pattern")
except UnsupportedPatternError as e:
print(f"Pattern error: {e}")
# Output: Invalid pattern string format: 'Invalid_Pattern'.
# Expected format: Noise_PATTERN_DH_CIPHER_HASH

# Handle state errors
try:
hs = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
hs.write_message() # Error: role not set
except RoleNotSetError as e:
print(f"State error: {e}")
# Output: Cannot write handshake message: role not set.
# Call set_as_initiator() or set_as_responder() first.

# Handle authentication failures
try:
ciphertext_tampered = ciphertext[:-1] + b"\x00"
transport.receive(ciphertext_tampered)
except AuthenticationError as e:
print(f"Authentication failed: {e}")
# DO NOT process the message - discard it

# Catch all framework errors
try:
# ... noise operations ...
except NoiseError as e:
print(f"Framework error: {type(e).__name__}: {e}")
```

**Available exceptions**: `NoiseError` (base), `HandshakeError`, `RoleNotSetError`, `RoleAlreadySetError`, `WrongTurnError`, `HandshakeCompleteError`, `MissingKeyError`, `PatternError`, `UnsupportedPatternError`, `UnsupportedPrimitiveError`, `StateError`, `NoKeySetError`, `NonceOverflowError`, `InvalidKeySizeError`, `TransportError`, `AuthenticationError`, `CryptoError`, `ValidationError`, `FramingError`.

See `examples/error_handling_example.py` for comprehensive error handling examples.

---

### Logging

NoiseFramework includes comprehensive logging support for debugging and monitoring. All major operations are logged at appropriate levels.

#### Basic Logging Setup

```python
import logging
from noiseframework import NoiseHandshake, NoiseTransport

# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)-8s] %(name)s: %(message)s"
)

# Use NoiseFramework normally - logging happens automatically
handshake = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
handshake.set_as_initiator() # Logs: "Role set as INITIATOR"
handshake.generate_static_keypair() # Logs: "Generated static keypair"
handshake.initialize() # Logs: "Handshake initialized"

msg = handshake.write_message(b"") # Logs: "Sent handshake message 1"
```

#### Log Levels

- **DEBUG**: Detailed operations (message sizes, nonces, token processing, key operations)
- **INFO**: Major events (role changes, handshake completion, message send/receive)
- **WARNING**: Potential issues (nonce approaching limit)
- **ERROR**: Failures (validation errors, authentication failures)

#### Custom Logger

```python
# Create custom logger with specific configuration
custom_logger = logging.getLogger("myapp.noise")
custom_logger.setLevel(logging.DEBUG)

# Add custom handler
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
custom_logger.addHandler(handler)

# Pass custom logger to NoiseHandshake
handshake = NoiseHandshake(
"Noise_XX_25519_ChaChaPoly_SHA256",
logger=custom_logger
)

# Pass custom logger to NoiseTransport
transport = NoiseTransport(send_cipher, recv_cipher, logger=custom_logger)
```

#### Filtering Logs by Module

```python
# Only show INFO+ logs from handshake module
logging.getLogger("noiseframework.noise.handshake").setLevel(logging.INFO)

# Show DEBUG logs from transport module
logging.getLogger("noiseframework.transport.transport").setLevel(logging.DEBUG)

# Disable logs from state module
logging.getLogger("noiseframework.noise.state").setLevel(logging.WARNING)
```

#### Example Log Output

```
2025-11-24 15:30:01 [INFO ] noiseframework.noise.handshake.NoiseHandshake: Role set as INITIATOR
2025-11-24 15:30:01 [DEBUG ] noiseframework.noise.handshake.NoiseHandshake: Generating static keypair (Curve25519)
2025-11-24 15:30:01 [INFO ] noiseframework.noise.handshake.NoiseHandshake: Generated static keypair
2025-11-24 15:30:01 [INFO ] noiseframework.noise.handshake.NoiseHandshake: Handshake initialized
2025-11-24 15:30:01 [DEBUG ] noiseframework.noise.handshake.NoiseHandshake: Writing handshake message 1 (payload=0 bytes)
2025-11-24 15:30:01 [INFO ] noiseframework.noise.handshake.NoiseHandshake: Sent handshake message 1 (ciphertext=32 bytes)
2025-11-24 15:30:02 [INFO ] noiseframework.noise.handshake.NoiseHandshake: Handshake complete - ready for transport mode
2025-11-24 15:30:02 [INFO ] noiseframework.noise.handshake.NoiseHandshake: Created transport ciphers (initiator: send=c1, receive=c2)
2025-11-24 15:30:03 [INFO ] noiseframework.transport.transport.NoiseTransport: Sent encrypted message (ciphertext=29 bytes)
2025-11-24 15:30:03 [WARNING ] noiseframework.transport.transport.NoiseTransport: Send cipher nonce high: 9223372036854775808 (approaching 2^64 limit - consider rekeying)
```

See [`examples/logging_example.py`](examples/logging_example.py) for more detailed examples.

### Message Framing

When using Noise over stream-based transports (TCP, pipes, etc.), you need a way to preserve message boundaries. NoiseFramework provides length-prefixed framing utilities for this purpose.

#### Basic Usage

```python
import socket
from noiseframework import NoiseHandshake, FramedWriter, FramedReader

# After handshake completes...
transport = handshake.to_transport()

# Wrap socket for framed communication
writer = FramedWriter(sock.makefile('wb'))
reader = FramedReader(sock.makefile('rb'))

# Send framed encrypted messages
ciphertext = transport.send(b"Hello, World!")
writer.write_message(ciphertext)

# Receive framed encrypted messages
framed_message = reader.read_message()
plaintext = transport.receive(framed_message)
```

#### Frame Format

NoiseFramework uses a simple 4-byte big-endian length prefix:

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Length (4B) โ”‚ Message Data โ”‚
โ”‚ big-endian โ”‚ (0 to 2^32-1 B) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

#### Configuration

```python
# Default maximum message size is 16 MB
reader = FramedReader(stream) # max_message_size=16*1024*1024

# Set custom maximum
reader = FramedReader(stream, max_message_size=1024*1024) # 1 MB limit

# Add logging
import logging
logger = logging.getLogger("myapp.framing")
reader = FramedReader(stream, logger=logger)
writer = FramedWriter(stream, logger=logger)
```

#### TCP Example

```python
import socket
from noiseframework import NoiseHandshake, FramedWriter, FramedReader

# Server
def server(port):
with socket.socket() as sock:
sock.bind(('localhost', port))
sock.listen(1)
conn, _ = sock.accept()

# Noise handshake (responder)
hs = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
hs.set_as_responder()
hs.generate_static_keypair()
hs.initialize()

reader = FramedReader(conn.makefile('rb'))
writer = FramedWriter(conn.makefile('wb'))

# Handshake messages with framing
msg1 = reader.read_message()
msg2 = hs.read_message(msg1)
msg2_out = hs.write_message(msg2)
writer.write_message(msg2_out)

msg3 = reader.read_message()
hs.read_message(msg3)

# Transport mode
transport = hs.to_transport()
encrypted = reader.read_message()
plaintext = transport.receive(encrypted)
print(f"Server received: {plaintext}")

# Client
def client(port):
with socket.socket() as sock:
sock.connect(('localhost', port))

# Noise handshake (initiator)
hs = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
hs.set_as_initiator()
hs.generate_static_keypair()
hs.initialize()

reader = FramedReader(sock.makefile('rb'))
writer = FramedWriter(sock.makefile('wb'))

# Handshake messages with framing
msg1 = hs.write_message(b"")
writer.write_message(msg1)

msg2 = reader.read_message()
msg3_payload = hs.read_message(msg2)
msg3 = hs.write_message(msg3_payload)
writer.write_message(msg3)

# Transport mode
transport = hs.to_transport()
ciphertext = transport.send(b"Hello, Server!")
writer.write_message(ciphertext)
```

#### Convenience Functions

For single-message operations:

```python
from noiseframework import write_framed_message, read_framed_message

# Write single framed message
write_framed_message(stream, b"Hello")

# Read single framed message
message = read_framed_message(stream)

# With custom max size
message = read_framed_message(stream, max_message_size=1024*1024)
```

#### Error Handling

```python
from noiseframework import FramingError

try:
message = reader.read_message()
except FramingError as e:
# Connection closed, oversized message, or invalid frame
print(f"Framing error: {e}")
except IOError as e:
# Underlying stream error
print(f"IO error: {e}")
```

**Key Points:**
- Automatic handling of partial reads
- Protection against oversized messages (configurable limit)
- Message counters for debugging (`messages_sent`, `messages_received`)
- Thread-safe for concurrent read/write on different threads
- Works with any byte stream (sockets, files, pipes, etc.)

See [`examples/framed_tcp_example.py`](examples/framed_tcp_example.py) for a complete working example.

### Async/Await Support

NoiseFramework provides full `asyncio` support for modern async Python applications. All async classes wrap the synchronous implementation using `run_in_executor`, making them safe for use in async contexts without blocking the event loop.

#### Basic Async Usage

```python
import asyncio
from noiseframework import AsyncNoiseHandshake, AsyncNoiseTransport

async def async_handshake():
# Create async handshake
handshake = AsyncNoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
await handshake.set_as_initiator()
await handshake.generate_static_keypair()
await handshake.initialize()

# Perform handshake (async)
msg1 = await handshake.write_message(b"")
# ... exchange messages over network ...

# Convert to async transport
transport = await handshake.to_transport()

# Send/receive encrypted messages (async)
ciphertext = await transport.send(b"Hello, async world!")
plaintext = await transport.receive(ciphertext)

# Run the async function
asyncio.run(async_handshake())
```

#### Async TCP Server Example

```python
import asyncio
from noiseframework import (
AsyncNoiseHandshake,
AsyncFramedReader,
AsyncFramedWriter,
)

async def handle_client(reader, writer):
"""Handle incoming client connection."""
# Create responder handshake
handshake = AsyncNoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
await handshake.set_as_responder()
await handshake.generate_static_keypair()
await handshake.initialize()

# Wrap streams with framing
framed_reader = AsyncFramedReader(reader)
framed_writer = AsyncFramedWriter(writer)

# Perform XX handshake
msg1 = await framed_reader.read_message()
await handshake.read_message(msg1)

msg2 = await handshake.write_message(b"")
await framed_writer.write_message(msg2)

msg3 = await framed_reader.read_message()
await handshake.read_message(msg3)

# Switch to transport mode
transport = await handshake.to_transport()

# Receive and process encrypted messages
while True:
try:
ciphertext = await framed_reader.read_message()
plaintext = await transport.receive(ciphertext)
print(f"Received: {plaintext.decode()}")

# Send encrypted response
response = await transport.send(b"Message received!")
await framed_writer.write_message(response)
except:
break

await framed_writer.close()

async def main():
server = await asyncio.start_server(
handle_client, '127.0.0.1', 9999
)
async with server:
await server.serve_forever()

asyncio.run(main())
```

#### Async TCP Client Example

```python
async def async_client():
# Connect to server
reader, writer = await asyncio.open_connection('127.0.0.1', 9999)

# Create initiator handshake
handshake = AsyncNoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
await handshake.set_as_initiator()
await handshake.generate_static_keypair()
await handshake.initialize()

# Wrap streams with framing
framed_reader = AsyncFramedReader(reader)
framed_writer = AsyncFramedWriter(writer)

# Perform XX handshake (3 messages)
msg1 = await handshake.write_message(b"")
await framed_writer.write_message(msg1)

msg2 = await framed_reader.read_message()
await handshake.read_message(msg2)

msg3 = await handshake.write_message(b"")
await framed_writer.write_message(msg3)

# Switch to transport mode
transport = await handshake.to_transport()

# Send encrypted messages
for i in range(3):
message = f"Message {i+1}".encode()
ciphertext = await transport.send(message)
await framed_writer.write_message(ciphertext)

response_ct = await framed_reader.read_message()
response = await transport.receive(response_ct)
print(f"Server response: {response.decode()}")

await framed_writer.close()

asyncio.run(async_client())
```

#### Async Framing

For asyncio streams, use `AsyncFramedReader` and `AsyncFramedWriter`:

```python
from noiseframework import AsyncFramedReader, AsyncFramedWriter

async def async_framed_communication(reader, writer):
framed_writer = AsyncFramedWriter(writer)
framed_reader = AsyncFramedReader(reader)

# Write framed message
await framed_writer.write_message(b"Hello, async!")

# Read framed message
message = await framed_reader.read_message()

# Close when done
await framed_writer.close()
```

#### Async Convenience Functions

```python
from noiseframework import (
async_write_framed_message,
async_read_framed_message,
)

# Write single message
await async_write_framed_message(writer, b"Hello")

# Read single message
message = await async_read_framed_message(reader)
```

#### Async API Classes

**AsyncNoiseHandshake**: Async wrapper for `NoiseHandshake`
- `await set_as_initiator()` - Set role as initiator
- `await set_as_responder()` - Set role as responder
- `await generate_static_keypair()` - Generate static keys
- `await set_static_keypair(private, public)` - Set existing keys
- `await set_remote_static_public_key(public)` - Set remote's public key
- `await initialize()` - Initialize handshake state
- `await write_message(payload)` - Write handshake message
- `await read_message(message)` - Read handshake message
- `await to_transport()` - Get AsyncNoiseTransport after completion
- `await get_handshake_hash()` - Get handshake hash
- `.is_complete` - Check if handshake is complete (property)

**AsyncNoiseTransport**: Async wrapper for `NoiseTransport`
- `await send(plaintext, ad=b"")` - Encrypt and send message
- `await receive(ciphertext, ad=b"")` - Decrypt and receive message
- `.send_nonce` - Current send nonce (property)
- `.receive_nonce` - Current receive nonce (property)

**AsyncFramedReader**: Async framed message reader
- `await read_message()` - Read length-prefixed message
- `await close()` - Close reader
- `.messages_received` - Message counter (property)

**AsyncFramedWriter**: Async framed message writer
- `await write_message(message)` - Write length-prefixed message
- `await close()` - Close writer and wait for completion
- `.messages_sent` - Message counter (property)

**Key Points:**
- All async operations use `run_in_executor` internally
- No blocking calls in the async event loop
- Compatible with `asyncio.StreamReader` and `asyncio.StreamWriter`
- Same security guarantees as synchronous version
- Logging support in all async classes

See [`examples/async_tcp_example.py`](examples/async_tcp_example.py) for a complete working example with server and client.

---

### Pre-Shared Key (PSK) Support

NoiseFramework supports Pre-Shared Key (PSK) patterns for quantum-resistant authentication. PSK patterns add an additional layer of security by mixing a shared secret into the handshake, providing protection against future quantum computer attacks on Diffie-Hellman key exchange.

#### What are PSK Patterns?

PSK patterns combine traditional Noise handshakes with pre-shared keys:
- **Quantum Resistance**: PSKs are immune to quantum computer attacks (unlike DH)
- **Additional Authentication**: Extra authentication layer beyond public keys
- **Pre-computation Resistance**: Attackers can't pre-compute attacks
- **Common Use Cases**: IoT devices, enterprise VPNs, defense systems, embedded systems

#### PSK Pattern Format

PSK patterns use modifiers (`psk0` through `psk4`) that indicate when the PSK is mixed:

```
Noise_XXpsk3_25519_ChaChaPoly_SHA256 # XX with PSK after third message
Noise_NNpsk0_25519_ChaChaPoly_SHA256 # NN with PSK before first message
Noise_IKpsk2_448_AESGCM_BLAKE2b # IK with PSK after second message
```

**PSK Modifiers:**
- `psk0`: PSK mixed before first message (maximum quantum resistance)
- `psk1`: PSK mixed after first message
- `psk2`: PSK mixed after second message (common for IK patterns)
- `psk3`: PSK mixed after third message (most common - XXpsk3)
- `psk4`: PSK mixed after fourth message (rare)

#### Basic PSK Usage

```python
import os
from noiseframework import NoiseHandshake, NoiseTransport

# Generate or load a 32-byte pre-shared key
psk = os.urandom(32) # Must be exchanged securely out-of-band

# Initiator
initiator = NoiseHandshake("Noise_NNpsk0_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.set_psk(psk) # Set PSK before initialize()
initiator.initialize()

# Responder
responder = NoiseHandshake("Noise_NNpsk0_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.set_psk(psk) # Must use same PSK
responder.initialize()

# Perform handshake (PSK mixed automatically)
msg1 = initiator.write_message(b"")
responder.read_message(msg1)

msg2 = responder.write_message(b"")
initiator.read_message(msg2)

# Create transport - now quantum-resistant!
init_send, init_recv = initiator.to_transport()
resp_send, resp_recv = responder.to_transport()

init_transport = NoiseTransport(init_send, init_recv)
resp_transport = NoiseTransport(resp_send, resp_recv)

# Secure communication
ciphertext = init_transport.send(b"Quantum-resistant message!")
plaintext = resp_transport.receive(ciphertext)
```

#### XXpsk3 - Most Common PSK Pattern

The `XXpsk3` pattern (mutual authentication + late PSK) is the most commonly used PSK pattern:

```python
psk = os.urandom(32)

# Initiator
initiator = NoiseHandshake("Noise_XXpsk3_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.generate_static_keypair() # XX requires static keys
initiator.set_psk(psk)
initiator.initialize()

# Responder
responder = NoiseHandshake("Noise_XXpsk3_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.generate_static_keypair()
responder.set_psk(psk)
responder.initialize()

# Three-message handshake
msg1 = initiator.write_message(b"")
responder.read_message(msg1)

msg2 = responder.write_message(b"")
initiator.read_message(msg2)

msg3 = initiator.write_message(b"") # PSK mixed here
responder.read_message(msg3)

# Both parties mutually authenticated + quantum-resistant
assert initiator.remote_static_public == responder.static_public
assert responder.remote_static_public == initiator.static_public
```

#### PSK with Async/Await

PSK works seamlessly with async code:

```python
async def async_psk_example():
psk = os.urandom(32)

handshake = AsyncNoiseHandshake("Noise_XXpsk3_25519_ChaChaPoly_SHA256")
await handshake.set_as_initiator()
await handshake.generate_static_keypair()
await handshake.set_psk(psk) # Async PSK setting
await handshake.initialize()

# Continue with async handshake...
```

#### PSK Security Considerations

**When to Use PSK:**
- You need quantum resistance
- You can securely exchange a shared secret out-of-band
- You're building IoT or embedded systems
- You want additional authentication beyond public keys

**PSK Management:**
- PSKs must be exchanged securely (never over an insecure channel)
- Use strong randomness (32 bytes from `os.urandom()`)
- PSKs can be safely reused across sessions
- Consider key rotation policies for long-lived PSKs

**Placement Trade-offs:**
- **Early PSK (psk0)**: Maximum quantum resistance, protects entire handshake
- **Late PSK (psk3)**: Allows public key exchange first, then adds PSK protection

See [`examples/psk_example.py`](examples/psk_example.py) for complete working examples of NNpsk0, XXpsk3, and IKpsk2 patterns.

---

### Fallback Pattern Support

NoiseFramework supports fallback patterns for graceful handshake degradation when the initiator's first message cannot be decrypted. This implements the **Noise Pipes protocol** (Section 10.2 of the Noise spec).

#### What are Fallback Patterns?

Fallback patterns enable recovery from handshake failures without dropping the connection:
- **Graceful Degradation**: When IK/NK fails, fallback to XX pattern
- **Key Rotation**: Handle responder static key changes
- **PSK Outdated**: Recover from outdated pre-shared keys
- **Role Reversal**: Responder becomes effective initiator in fallback

#### Noise Pipes Protocol (IK โ†’ XXfallback)

The most common fallback scenario is **Noise Pipes**: Alice attempts IK, Bob detects wrong static key and falls back to XXfallback.

```python
import os
from noiseframework import NoiseHandshake

# Bob generates his real keys
bob = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
bob.set_as_responder()
bob.generate_static_keypair()
bob.initialize()

# Alice attempts IK with WRONG Bob static key (outdated)
alice = NoiseHandshake("Noise_IK_25519_ChaChaPoly_SHA256")
alice.set_as_initiator()
alice.generate_static_keypair()
alice.set_remote_static_public_key(wrong_bob_key) # Outdated key
alice.initialize()

# Alice sends IK first message - will fail decryption
ik_msg1 = alice.write_message(b"Hello Bob")

# Bob cannot decrypt - extract Alice's ephemeral key (first 32 bytes)
alice_ephemeral = ik_msg1[:32]

# Bob initiates fallback to XXfallback
bob.start_fallback(alice_ephemeral)

# Bob now sends first XXfallback message (role reversal)
fallback_msg1 = bob.write_message(b"Fallback initiated")

# Alice detects IK failure and switches to XXfallback
alice_fallback = NoiseHandshake("Noise_XXfallback_25519_ChaChaPoly_SHA256")
alice_fallback.set_as_initiator()
alice_fallback.set_static_keypair(alice.static_private, alice.static_public)

# Reuse Alice's ephemeral keys (XXfallback uses them as pre-message)
alice_fallback.ephemeral_private = alice.ephemeral_private
alice_fallback.ephemeral_public = alice.ephemeral_public
alice_fallback.initialize()

# Alice reads Bob's fallback message
payload1 = alice_fallback.read_message(fallback_msg1)

# Alice sends her static key (second XXfallback message)
fallback_msg2 = alice_fallback.write_message(b"Acknowledged")

# Bob reads Alice's response - handshake complete!
payload2 = bob.read_message(fallback_msg2)

# Create transport channels
bob_send, bob_recv = bob.to_transport()
alice_send, alice_recv = alice_fallback.to_transport()

# Secure communication established despite initial IK failure!
```

#### Fallback Mechanics

**Pattern Transformation:**
```
XX โ†’ XXfallback # First message "e" becomes pre-message
NN โ†’ NNfallback # First message "e" becomes pre-message
IK โ†’ XXfallback # Cannot use IKfallback (first message contains DH)
NK โ†’ XXfallback # Cannot use NKfallback (first message contains DH)
```

**Valid Fallback Patterns:**
- Fallback requires initiator's first message to be "e", "s", or "e, s"
- Cannot fallback patterns where first message contains DH operations (es, se, ss, ee)

**Fallback Process:**
1. Responder receives initiator's first message but cannot decrypt
2. Responder extracts initiator's ephemeral key from failed message
3. Responder calls `start_fallback(remote_ephemeral_public_key)`
4. Pattern switches (e.g., XX โ†’ XXfallback), state re-initialized
5. Responder sends first message (role reversal)
6. Initiator also switches to fallback pattern
7. Handshake completes normally with fallback pattern

#### API Usage

```python
# Responder only - call when decryption fails
handshake.start_fallback(remote_ephemeral_public_key: bytes) -> None

# Async version
await handshake.start_fallback(remote_ephemeral_public_key: bytes)

# Example error handling with fallback
try:
payload = responder.read_message(initiator_msg1)
except Exception:
# Extract ephemeral key (first DHLEN bytes)
alice_ephemeral = initiator_msg1[:32] # 32 for Curve25519
responder.start_fallback(alice_ephemeral)
# Continue with fallback pattern...
```

#### Security Considerations

**When to Use Fallback:**
- Server static key rotation scenarios
- PSK may be outdated but fallback acceptable
- Graceful degradation preferred over connection failure

**Fallback Limitations:**
- Both parties must coordinate fallback (initiator must also switch)
- Ephemeral key must be preserved from failed message
- Only works with patterns where first message can be pre-message

**Noise Pipes Use Case:**
The IK โ†’ XXfallback pattern is standardized as "Noise Pipes" and widely used for:
- IoT device provisioning (device has outdated server key)
- Server key rotation (clients gradually update)
- Fallback from authenticated to mutual authentication

See [`examples/fallback_example.py`](examples/fallback_example.py) for a complete working demonstration of the Noise Pipes protocol with IK โ†’ XXfallback.

---

### High-Level Connection API

For most applications, the high-level `NoiseConnection` and `AsyncNoiseConnection` classes provide the simplest way to establish secure connections. These classes automatically handle handshakes, transport mode transitions, and message framing.

#### Synchronous Connection Example

```python
from noiseframework import NoiseConnection
import socket
import threading

def server():
"""Responder side."""
server_sock = socket.socket()
server_sock.bind(("localhost", 9999))
server_sock.listen(1)
client_sock, _ = server_sock.accept()

# Create connection and accept - handshake happens automatically
with NoiseConnection("Noise_XX_25519_ChaChaPoly_SHA256", "responder") as conn:
conn.accept(client_sock)

# Now in transport mode - send/receive encrypted messages
data = conn.receive()
conn.send(b"Echo: " + data)

server_sock.close()

# Start server in background
threading.Thread(target=server, daemon=True).start()

# Client (initiator side)
with NoiseConnection("Noise_XX_25519_ChaChaPoly_SHA256", "initiator") as conn:
conn.connect(("localhost", 9999)) # Handshake happens automatically

conn.send(b"Hello, NoiseFramework!")
response = conn.receive()
print(response) # b"Echo: Hello, NoiseFramework!"
```

#### Asynchronous Connection Example

```python
import asyncio
from noiseframework import AsyncNoiseConnection

async def handle_client(reader, writer):
"""Async responder."""
async with AsyncNoiseConnection("Noise_XX_25519_ChaChaPoly_SHA256", "responder") as conn:
await conn.accept_streams(reader, writer) # Auto handshake

data = await conn.receive()
await conn.send(b"Echo: " + data)

async def main():
# Start async server
server = await asyncio.start_server(handle_client, "localhost", 9999)

# Client
async with AsyncNoiseConnection("Noise_XX_25519_ChaChaPoly_SHA256", "initiator") as conn:
await conn.connect(("localhost", 9999)) # Auto handshake

await conn.send(b"Hello, async!")
response = await conn.receive()
print(response) # b"Echo: Hello, async!"

server.close()
await server.wait_closed()

asyncio.run(main())
```

#### Connection API Features

**NoiseConnection** (sync) and **AsyncNoiseConnection** (async):

```python
# Constructor
conn = NoiseConnection(
pattern="Noise_XX_25519_ChaChaPoly_SHA256",
role="initiator", # or "responder"
static_private_key=None, # Optional: use custom keys
static_public_key=None,
remote_static_public_key=None, # For IK/NK patterns
max_message_size=16*1024*1024, # 16 MB default
logger=None # Optional logging
)

# Initiator methods
conn.connect(("host", port)) # Sync
await conn.connect(("host", port)) # Async

# Responder methods
conn.accept(client_socket) # Sync
await conn.accept_streams(reader, writer) # Async

# Communication (same for both roles)
conn.send(plaintext_bytes) # Sync
await conn.send(plaintext_bytes) # Async

plaintext = conn.receive() # Sync
plaintext = await conn.receive() # Async

# Connection state
if conn.is_connected:
print("Connected and handshake complete")

# Identity verification
remote_key = conn.remote_static_public_key # Get remote's identity
local_key = conn.local_static_public_key # Get our identity

# Cleanup
conn.close() # Sync
await conn.close() # Async

# Or use context managers (automatic cleanup)
with NoiseConnection(...) as conn:
# ... use conn ...
# Closes automatically

async with AsyncNoiseConnection(...) as conn:
# ... use conn ...
# Closes automatically
```

#### Benefits of Connection API

- โœ… **Automatic handshake** - No manual `write_message()`/`read_message()` calls
- โœ… **Automatic framing** - Built-in length-prefixed message framing
- โœ… **Automatic transport** - Seamless transition from handshake to encryption
- โœ… **Simple lifecycle** - Just `connect()`/`accept()`, `send()`, `receive()`, `close()`
- โœ… **Identity access** - Easy access to local and remote public keys
- โœ… **Context managers** - Automatic cleanup with `with` statements
- โœ… **Clear errors** - ValidationError, HandshakeError, TransportError

#### When to Use Each API

**Use `NoiseConnection`/`AsyncNoiseConnection` when:**
- Building complete secure connections (most common use case)
- You want simplicity and automatic handling
- Standard patterns (XX, IK, NK, etc.) are sufficient

**Use `NoiseHandshake` + `NoiseTransport` when:**
- You need fine control over handshake steps
- Implementing custom protocols on top of Noise
- Building non-standard integrations

See [`examples/connection_example.py`](examples/connection_example.py) for complete examples including custom keys and identity verification.

---

## ๐Ÿ–ฅ๏ธ CLI Documentation

The `NoiseFramework` command-line tool provides easy access to key operations without writing code.

### Generate Keypair

Generate static keypairs for use in Noise handshakes:

```bash
# Generate Curve25519 keypair (default)
noiseframework generate-keypair -o mykey
# Creates: mykey_private.key (32 bytes), mykey_public.key (32 bytes)

# Generate Curve448 keypair
noiseframework generate-keypair --dh 448 -o mykey448
# Creates: mykey448_private.key (56 bytes), mykey448_public.key (56 bytes)

# Use short alias
noiseframework genkey -o server_key
```

**Output:**
```
Generated keypair:
Private key: mykey_private.key
Public key: mykey_public.key
Key size: 32 bytes
```

**Usage in Python:**
```python
from pathlib import Path
from noiseframework import NoiseHandshake

# Load generated keys
private_key = Path("mykey_private.key").read_bytes()
public_key = Path("mykey_public.key").read_bytes()

# Use in handshake
hs = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
hs.set_static_keypair(private_key, public_key)
```

### Validate Pattern

Validate Noise pattern strings and view their components:

```bash
# Validate a pattern
noiseframework validate-pattern "Noise_XX_25519_ChaChaPoly_SHA256"

# Use short alias
noiseframework validate "Noise_IK_448_AESGCM_BLAKE2b"
```

**Output:**
```
Pattern: Noise_XX_25519_ChaChaPoly_SHA256
Valid: โœ“
Name: Noise_XX_25519_ChaChaPoly_SHA256
Handshake: XX
DH: 25519
Cipher: ChaChaPoly
Hash: SHA256
```

**Invalid pattern:**
```bash
noiseframework validate "Noise_INVALID_Pattern"
# Error: Invalid pattern: Unsupported handshake pattern: INVALID
```

### Show Information

Display supported cryptographic primitives and patterns:

```bash
noiseframework info
```

**Output:**
```
NoiseFramework - Noise Protocol Framework Implementation

Supported DH functions:
- 25519 (Curve25519/X25519)
- 448 (Curve448/X448)

Supported ciphers:
- ChaChaPoly (ChaCha20-Poly1305) [recommended]
- AESGCM (AES-256-GCM)

Supported hash functions:
- SHA256 [recommended]
- SHA512
- BLAKE2s
- BLAKE2b

Supported patterns:
NN, NK, NX, KN, KK, KX, XN, XK, XX, IN, IK, IX

Example pattern string:
Noise_XX_25519_ChaChaPoly_SHA256
```

### Help and Version

```bash
# Show help
noiseframework --help
noiseframework generate-keypair --help

# Show version
noiseframework --version
```

---

## ๐Ÿ” Supported Patterns

NoiseFramework supports all fundamental and interactive Noise patterns:

| Pattern | Description | Use Case |
|---------|-------------|----------|
| `NN` | No static keys | Anonymous communication |
| `KN` | Initiator known | Server authentication |
| `NK` | Responder known | Client knows server's key |
| `KK` | Both known | Pre-shared public keys |
| `NX` | Responder transmits | Certificate-like exchange |
| `KX` | Initiator known, responder transmits | Hybrid authentication |
| `XN` | Initiator transmits | Basic server setup |
| `IN` | Initiator identity hidden | Privacy-preserving |
| `XK` | Responder known, initiator transmits | Standard mutual auth |
| `IK` | Responder known, initiator identity hidden | Tor-like handshake |
| `XX` | Both transmit | Full mutual authentication |
| `IX` | Initiator identity hidden, responder transmits | Privacy + auth |

### Pattern Modifiers

- **`psk0`, `psk1`, `psk2`**: Pre-shared symmetric key modes
- **Fallback patterns**: For retry and downgrade scenarios

---

## ๐Ÿ”‘ Cryptographic Primitives

NoiseFramework uses battle-tested cryptographic libraries:

### Diffie-Hellman Functions
- **Curve25519** (X25519) - Recommended
- **Curve448** (X448)

### Cipher Functions (AEAD)
- **ChaChaPoly** (ChaCha20-Poly1305) - Recommended
- **AESGCM** (AES-256-GCM)

### Hash Functions
- **SHA-256** - Recommended
- **SHA-512**
- **BLAKE2s**
- **BLAKE2b**

**Example pattern string**: `Noise_XX_25519_ChaChaPoly_SHA256`

Format: `Noise_[PATTERN]_[DH]_[CIPHER]_[HASH]`

---

## ๐Ÿ—๏ธ Architecture

```
noiseframework/
โ”œโ”€โ”€ noiseframework/
โ”‚ โ”œโ”€โ”€ __init__.py # Public API
โ”‚ โ”œโ”€โ”€ exceptions.py # Custom exception hierarchy
โ”‚ โ”œโ”€โ”€ noise/
โ”‚ โ”‚ โ”œโ”€โ”€ handshake.py # Handshake state machine
โ”‚ โ”‚ โ”œโ”€โ”€ pattern.py # Pattern parser and validator
โ”‚ โ”‚ โ””โ”€โ”€ state.py # Cipher and symmetric state
โ”‚ โ”œโ”€โ”€ crypto/
โ”‚ โ”‚ โ”œโ”€โ”€ dh.py # Diffie-Hellman functions
โ”‚ โ”‚ โ”œโ”€โ”€ cipher.py # AEAD cipher implementations
โ”‚ โ”‚ โ””โ”€โ”€ hash.py # Hash function wrappers
โ”‚ โ”œโ”€โ”€ transport/
โ”‚ โ”‚ โ””โ”€โ”€ transport.py # Post-handshake encryption
โ”‚ โ”œโ”€โ”€ framing.py # Message framing utilities
โ”‚ โ”œโ”€โ”€ async_support.py # Async/await wrappers
โ”‚ โ””โ”€โ”€ cli/
โ”‚ โ””โ”€โ”€ main.py # Command-line interface
โ”œโ”€โ”€ tests/
โ”‚ โ”œโ”€โ”€ test_handshake.py
โ”‚ โ”œโ”€โ”€ test_transport.py
โ”‚ โ”œโ”€โ”€ test_pattern.py
โ”‚ โ”œโ”€โ”€ test_cipher.py
โ”‚ โ”œโ”€โ”€ test_exceptions.py # Exception tests
โ”‚ โ”œโ”€โ”€ test_logging.py # Logging tests
โ”‚ โ”œโ”€โ”€ test_framing.py # Framing tests
โ”‚ โ””โ”€โ”€ test_async.py # Async tests
โ”œโ”€โ”€ examples/
โ”‚ โ”œโ”€โ”€ basic_client_server.py
โ”‚ โ”œโ”€โ”€ simple_chat.py
โ”‚ โ”œโ”€โ”€ file_encryption.py
โ”‚ โ”œโ”€โ”€ error_handling_example.py
โ”‚ โ”œโ”€โ”€ logging_example.py
โ”‚ โ”œโ”€โ”€ framed_tcp_example.py
โ”‚ โ””โ”€โ”€ async_tcp_example.py
โ”œโ”€โ”€ docs/
โ”‚ โ”œโ”€โ”€ API.md
โ”‚ โ”œโ”€โ”€ CHANGELOG.md
โ”‚ โ”œโ”€โ”€ ARCHITECTURE.md
โ”‚ โ”œโ”€โ”€ SECURITY.md
โ”‚ โ””โ”€โ”€ ...
โ”œโ”€โ”€ pyproject.toml
โ””โ”€โ”€ README.md
```

---

## ๐Ÿงช Testing

NoiseFramework has comprehensive test coverage with **311 tests** achieving **100% pass rate**.

### Test Categories
- **Core functionality** (156 tests): Handshake, transport, patterns, crypto primitives
- **Exception handling** (15 tests): Custom exception hierarchy and error messages
- **Logging** (21 tests): Logging functionality across all components
- **Framing** (30 tests): Message framing for stream-based transports
- **Async support** (21 tests): Async/await functionality

---

## โšก Performance

NoiseFramework delivers production-ready performance with real-world benchmarks:

- **Handshakes**: ~1,500-1,800 complete handshakes/sec (XX pattern)
- **Transport encryption**: **3+ GB/s** throughput for large messages
- **Key generation**: ~32,000 keypairs/sec (Curve25519)
- **Latency**: <3 ยตs per small message encryption

### Quick Benchmark Results

| Operation | Performance |
|-----------|-------------|
| Complete XX handshake | 558-642 ยตs |
| Encrypt 64 KB message | 18.5 ยตs (3.29 GB/s) |
| Encrypt 1 KB message | 2.4 ยตs (403 MB/s) |
| Generate Curve25519 keypair | 31 ยตs |

**See [BENCHMARKS.md](docs/BENCHMARKS.md) for comprehensive performance analysis, methodology, and optimization tips.**

### Run Benchmarks Yourself

```bash
# Activate virtual environment
source .venv/bin/activate # Linux/macOS
# or
.\.venv\Scripts\Activate.ps1 # Windows PowerShell

# Run benchmark script
python benchmark.py
```

---

## ๐Ÿงช Testing (Detailed)

Run the test suite:

```bash
# Install test dependencies
pip install -e ".[dev]"

# Run all tests
pytest

# Run with coverage
pytest --cov=noiseframework --cov-report=html

# Run specific test file
pytest tests/test_handshake.py

# Run with verbose output
pytest -v
```

### Test Categories

- **Unit tests**: Test individual components in isolation
- **Integration tests**: Test complete handshake flows
- **Property-based tests**: Use Hypothesis for invariant testing
- **Vector tests**: Validate against official Noise test vectors

---

## ๐Ÿค Contributing

Contributions are welcome! Please follow these guidelines:

1. **Fork the repository** and create a feature branch
2. **Follow the coding style**: PEP 8, type hints, and existing conventions
3. **Write tests**: All new features must include tests
4. **Update documentation**: Add examples and update `CHANGELOG.md`
5. **Run the test suite**: Ensure all tests pass
6. **Submit a pull request**: Describe your changes clearly

See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed guidelines.

### Development Setup

```bash
git clone https://github.com/juliuspleunes4/noiseframework.git
cd noiseframework
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -e ".[dev]"
```

---

## โ“ FAQ

### Which pattern should I use?

- **XX**: Default choice for mutual authentication
- **NN**: Quick anonymous encryption (no authentication)
- **IK**: When client knows server's key in advance (like Tor)
- **NK**: When server identity is public (like HTTPS with pinning)

### Is NoiseFramework production-ready?

Yes, but with caveats:
- โœ… Cryptographically sound (uses battle-tested primitives)
- โœ… Specification-compliant implementation
- โœ… Well-tested (311 tests, 100% pass rate)
- โœ… Comprehensive error handling with helpful messages
- โœ… Production-ready logging and monitoring support
- โš ๏ธ Consider security audit for high-stakes applications
- โš ๏ธ Keep dependencies updated

### How does it compare to other Noise implementations?

- **PyNaCl/libsodium**: Lower-level, NoiseFramework is higher-level Noise protocol
- **noiseprotocol (Python)**: Similar, but NoiseFramework has better docs and CLI
- **snow (Rust)**: Faster, but NoiseFramework is pure Python with better accessibility

### Can I use custom cryptographic primitives?

Yes, you can extend the crypto modules. However, we strongly recommend using only well-vetted primitives from established libraries.

### Does it support post-quantum cryptography?

Not yet. Post-quantum Noise patterns (pqXX, etc.) are planned for future releases.

---

## ๐Ÿ”’ Security

### Reporting Vulnerabilities

If you discover a security vulnerability, please **DO NOT** open a public issue. Instead:

1. Email security concerns to: [jjgpleunes@gmail.com]
2. Include a detailed description and steps to reproduce
3. Allow reasonable time for a fix before public disclosure

### Security Best Practices

- **Key Management**: Never hard-code keys in source code
- **RNG**: Use system-provided cryptographically secure random number generators
- **Updates**: Keep NoiseFramework and its dependencies up-to-date
- **Audit**: Consider professional security audits for production use
- **Side-Channels**: Be aware of timing and other side-channel attacks

### Dependencies

NoiseFramework relies on:
- `cryptography` - Audited, well-maintained Python cryptography library
- No custom cryptographic primitives

---

## ๐Ÿ“„ License

This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.

---

## ๐Ÿ™ Acknowledgments

- **[Trevor Perrin](https://github.com/trevp)** - Creator of the Noise Protocol Framework
- **Noise Protocol Community** - For the specification and test vectors
- **PyCA Cryptography** - For providing robust cryptographic primitives

---

## ๐Ÿ“š Resources

- [Noise Protocol Framework Specification](https://noiseprotocol.org/noise.html)
- [Noise Explorer](https://noiseexplorer.com/) - Formal verification of Noise patterns
- [Noise Wiki](https://github.com/noiseprotocol/noise_wiki/wiki)
- [PyCA Cryptography Documentation](https://cryptography.io/)

---

## ๐Ÿš€ Roadmap & Future Features

NoiseFramework v1.3.0 is **feature-complete** with all planned production-readiness enhancements implemented. Future development is planned for v1.4.0 and beyond.

### Completed in v1.3.0 โœ…

All 7 major features have been implemented:
- โœ… Async/Await Support (21 tests)
- โœ… Message Framing (30 tests)
- โœ… Better Error Messages (15 tests)
- โœ… High-Level Connection API (25 tests)
- โœ… Logging Support (21 tests)
- โœ… PSK Support (22 tests)
- โœ… Fallback Pattern Support (21 tests)

### Planned for v1.4.0 ๐Ÿ”„

The next version will focus on advanced Noise Protocol Framework features:

1. **Rekey Support** - Prevent nonce exhaustion for long-lived connections
- Manual and automatic rekeying
- Essential for IoT, VPNs, and persistent servers
- Spec: Section 11.3

2. **Deferred Patterns** - Support NX, KX, IX patterns
- Responder identity revealed later in handshake
- Server-side optimization opportunities
- Spec: Section 10.4

3. **Channel Binding** - Link Noise session to application context
- Prevents session confusion attacks
- Required for some compliance scenarios
- Spec: Section 11.2

### Future Considerations (v1.5.0+) ๐Ÿ’ก

- Out-of-Order Transport (UDP support)
- Post-Quantum Cryptography (Kyber, hybrid schemes)
- Hardware Security Module (HSM) support
- Additional cipher suites
- Performance optimizations
- Protocol plugins/extensions

**๐Ÿ“‹ See [docs/TODO.md](docs/TODO.md) for detailed feature descriptions, planned APIs, and implementation roadmap.**

---

## ๐Ÿ“ž Support

- **Issues**: [GitHub Issues](https://github.com/juliuspleunes4/noiseframework/issues)
- **Discussions**: [GitHub Discussions](https://github.com/juliuspleunes4/noiseframework/discussions)
- **Documentation**: [Full Documentation](https://noiseframework.readthedocs.io/)

---


Built with โค๏ธ for secure communications


If you find this project useful, please consider giving it a โญ๏ธ