{"id":37065445,"url":"https://github.com/juliuspleunes4/noiseframework","last_synced_at":"2026-01-14T07:39:41.172Z","repository":{"id":324594263,"uuid":"1097785218","full_name":"juliuspleunes4/NoiseFramework","owner":"juliuspleunes4","description":"Noise Protocol Framework in Python.","archived":false,"fork":false,"pushed_at":"2025-11-25T09:07:45.000Z","size":574,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-27T12:55:58.874Z","etag":null,"topics":["cryptography","noise","pypi","python"],"latest_commit_sha":null,"homepage":"https://noiseframework.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juliuspleunes4.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-16T20:21:20.000Z","updated_at":"2025-11-25T08:41:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/juliuspleunes4/NoiseFramework","commit_stats":null,"previous_names":["juliuspleunes4/noiseframework"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/juliuspleunes4/NoiseFramework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliuspleunes4%2FNoiseFramework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliuspleunes4%2FNoiseFramework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliuspleunes4%2FNoiseFramework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliuspleunes4%2FNoiseFramework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juliuspleunes4","download_url":"https://codeload.github.com/juliuspleunes4/NoiseFramework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliuspleunes4%2FNoiseFramework/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","noise","pypi","python"],"created_at":"2026-01-14T07:39:40.432Z","updated_at":"2026-01-14T07:39:41.157Z","avatar_url":"https://github.com/juliuspleunes4.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NoiseFramework\n\n[![Official Website](https://img.shields.io/badge/🌐%20Official%20Website-www.noiseframework.com-4A90E2?style=for-the-badge\u0026logo=globe\u0026logoColor=white)](https://www.noiseframework.com)\n\u003cbr\u003e\n\n\u003cbr\u003e\n\n[![PyPI version](https://img.shields.io/pypi/v/noiseframework.svg)](https://pypi.org/project/noiseframework/)\n[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)\n[![GitHub Issues](https://img.shields.io/github/issues/juliuspleunes4/noiseframework)](https://github.com/juliuspleunes4/noiseframework/issues)\n[![GitHub Stars](https://img.shields.io/github/stars/juliuspleunes4/noiseframework)](https://github.com/juliuspleunes4/noiseframework/stargazers)\n[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\n\u003e A professional, secure, and easy-to-use implementation of the [Noise Protocol Framework](https://noiseprotocol.org/) in Python.\n\n**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.\n\n---\n\n## 📋 Table of Contents\n\n- [Features](#-features)\n- [Installation](#-installation)\n- [Quick Start](#-quick-start)\n  - [Python API](#python-api)\n  - [Command-Line Interface](#command-line-interface)\n- [Python API Documentation](#-python-api-documentation)\n  - [Basic Handshake (Noise_XX)](#basic-handshake-noise_xx)\n  - [Anonymous Pattern (Noise_NN)](#anonymous-pattern-noise_nn)\n  - [Pre-Shared Key Pattern (Noise_IK)](#pre-shared-key-pattern-noise_ik)\n  - [Transport Layer Encryption](#transport-layer-encryption)\n  - [Error Handling](#error-handling)\n  - [Logging](#logging)\n  - [Message Framing](#message-framing)\n  - [Async/Await Support](#asyncawait-support)\n  - [Pre-Shared Key (PSK) Support](#pre-shared-key-psk-support)\n  - [Fallback Pattern Support](#fallback-pattern-support)\n  - [High-Level Connection API](#high-level-connection-api)\n- [CLI Documentation](#-cli-documentation)\n  - [Generate Keypair](#generate-keypair)\n  - [Validate Pattern](#validate-pattern)\n  - [Show Information](#show-information)\n- [Supported Patterns](#-supported-patterns)\n- [Cryptographic Primitives](#-cryptographic-primitives)\n- [Architecture](#-architecture)\n- [Testing](#-testing)\n- [Performance](#-performance)\n- [Contributing](#-contributing)\n- [Security](#-security)\n- [FAQ](#-faq)\n- [License](#-license)\n- [Acknowledgments](#-acknowledgments)\n\n---\n\n## ✨ Features\n\n- **📜 Spec-Compliant**: Implements the [Noise Protocol Framework specification](https://noiseprotocol.org/noise.html) faithfully\n- **🔒 Secure by Default**: Uses well-vetted cryptographic primitives from trusted libraries\n- **🐍 Pythonic API**: Simple, type-hinted interfaces that are easy to use and hard to misuse\n- **🛠️ CLI Tool**: Command-line interface for encryption, decryption, and handshake operations\n- **✅ Well-Tested**: Comprehensive test suite with 311 tests achieving 100% pass rate\n- **📦 Zero Config**: Works out-of-the-box with sensible defaults\n- **🔧 Flexible**: Supports multiple DH functions, cipher suites, and hash functions\n- **🛡️ PSK Support**: Pre-Shared Key patterns for quantum-resistant authentication (psk0-psk4)\n- **📖 Documented**: Extensive documentation with examples and best practices\n- **🔍 Helpful Errors**: Custom exceptions with actionable error messages guide you to fix issues quickly\n- **📝 Built-in Logging**: Comprehensive logging support for debugging and monitoring\n- **📦 Message Framing**: Automatic length-prefixed framing for stream-based transports\n- **⚡ Async/Await**: Full async support for modern Python asyncio applications\n- **🔗 High-Level Connection API**: `NoiseConnection` and `AsyncNoiseConnection` for easy-to-use connection management\n\n---\n\n## 📦 Installation\n\n### From PyPI (Recommended)\n\n```bash\npip install noiseframework\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/juliuspleunes4/noiseframework.git\ncd noiseframework\npip install -e .\n```\n\n### Requirements\n\n- Python 3.8 or higher\n- Dependencies are automatically installed via pip\n\n---\n\n## 🚀 Quick Start\n\n### Python API\n\n```python\nfrom noiseframework import NoiseHandshake, NoiseTransport\n\n# === INITIATOR SIDE ===\ninitiator = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\ninitiator.set_as_initiator()\ninitiator.generate_static_keypair()\ninitiator.initialize()\n\n# === RESPONDER SIDE ===\nresponder = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\nresponder.set_as_responder()\nresponder.generate_static_keypair()\nresponder.initialize()\n\n# === HANDSHAKE ===\nmsg1 = initiator.write_message(b\"\")\nresponder.read_message(msg1)\n\nmsg2 = responder.write_message(b\"\")\ninitiator.read_message(msg2)\n\nmsg3 = initiator.write_message(b\"\")\nresponder.read_message(msg3)\n\n# === TRANSPORT ENCRYPTION ===\ninit_send, init_recv = initiator.to_transport()\nresp_send, resp_recv = responder.to_transport()\n\ninit_transport = NoiseTransport(init_send, init_recv)\nresp_transport = NoiseTransport(resp_send, resp_recv)\n\n# Send encrypted messages\nciphertext = init_transport.send(b\"Hello, secure world!\")\nplaintext = resp_transport.receive(ciphertext)\nprint(plaintext)  # b\"Hello, secure world!\"\n```\n\n### Command-Line Interface\n\n```bash\n# Generate a keypair\nnoiseframework generate-keypair --dh 25519 -o mykey\n# Creates: mykey_private.key, mykey_public.key\n\n# Validate a pattern string\nnoiseframework validate-pattern \"Noise_XX_25519_ChaChaPoly_SHA256\"\n\n# Show supported primitives\nnoiseframework info\n\n# Use shorter aliases\nnoiseframework genkey --dh 25519 -o mykey\nnoiseframework validate \"Noise_XX_25519_ChaChaPoly_SHA256\"\n```\n\n---\n\n## 📖 Python API Documentation\n\n### Basic Handshake (Noise_XX)\n\nThe `XX` pattern provides mutual authentication with no prior knowledge required. Both parties exchange static keys during the handshake.\n\n```python\nfrom noiseframework import NoiseHandshake, NoiseTransport\n\n# === INITIATOR SIDE ===\ninitiator = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\ninitiator.set_as_initiator()\ninitiator.generate_static_keypair()  # Generate static key\ninitiator.initialize()\n\n# Send first message (-\u003e e)\nmsg1 = initiator.write_message(b\"\")\n\n# === RESPONDER SIDE ===\nresponder = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\nresponder.set_as_responder()\nresponder.generate_static_keypair()  # Generate static key\nresponder.initialize()\n\n# Process first message and send response (-\u003e e, ee, s, es)\nresponder.read_message(msg1)\nmsg2 = responder.write_message(b\"\")\n\n# === INITIATOR SIDE (continued) ===\n# Process second message and send final (-\u003e s, se)\ninitiator.read_message(msg2)\nmsg3 = initiator.write_message(b\"\")\n\n# === RESPONDER SIDE (continued) ===\n# Process final message\nresponder.read_message(msg3)\n\n# === BOTH SIDES NOW HAVE SECURE CHANNEL ===\n# Get transport cipher pairs\ninit_send, init_recv = initiator.to_transport()\nresp_send, resp_recv = responder.to_transport()\n\n# Create transport wrappers\ninit_transport = NoiseTransport(init_send, init_recv)\nresp_transport = NoiseTransport(resp_send, resp_recv)\n\n# Send encrypted data (initiator -\u003e responder)\nciphertext = init_transport.send(b\"Secret payload\")\nplaintext = resp_transport.receive(ciphertext)\nassert plaintext == b\"Secret payload\"\n\n# Send encrypted data (responder -\u003e initiator)\nciphertext = resp_transport.send(b\"Response data\")\nplaintext = init_transport.receive(ciphertext)\nassert plaintext == b\"Response data\"\n```\n\n### Anonymous Pattern (Noise_NN)\n\nThe `NN` pattern provides encryption without authentication. No static keys are required.\n\n```python\nfrom noiseframework import NoiseHandshake, NoiseTransport\n\n# === INITIATOR SIDE ===\ninitiator = NoiseHandshake(\"Noise_NN_25519_ChaChaPoly_SHA256\")\ninitiator.set_as_initiator()\ninitiator.initialize()\n\n# Send first message (-\u003e e)\nmsg1 = initiator.write_message(b\"\")\n\n# === RESPONDER SIDE ===\nresponder = NoiseHandshake(\"Noise_NN_25519_ChaChaPoly_SHA256\")\nresponder.set_as_responder()\nresponder.initialize()\n\n# Process first message and send response (-\u003e e, ee)\nresponder.read_message(msg1)\nmsg2 = responder.write_message(b\"\")\n\n# === INITIATOR SIDE (continued) ===\n# Process second message - handshake complete\ninitiator.read_message(msg2)\n\n# === CREATE TRANSPORT ===\ninit_send, init_recv = initiator.to_transport()\nresp_send, resp_recv = responder.to_transport()\n\ninit_transport = NoiseTransport(init_send, init_recv)\nresp_transport = NoiseTransport(resp_send, resp_recv)\n\n# Now both sides can communicate securely (but without authentication)\nciphertext = init_transport.send(b\"Anonymous message\")\nplaintext = resp_transport.receive(ciphertext)\n```\n\n### Pre-Shared Key Pattern (Noise_IK)\n\nThe `IK` pattern allows the initiator to know the responder's static public key in advance. The initiator's identity is hidden.\n\n```python\nfrom noiseframework import NoiseHandshake, NoiseTransport\n\n# === SETUP: Generate responder's static keypair ===\nresponder_setup = NoiseHandshake(\"Noise_IK_25519_ChaChaPoly_SHA256\")\nresponder_setup.set_as_responder()\nresponder_setup.generate_static_keypair()\nresponder_private = responder_setup.static_private\nresponder_public = responder_setup.static_public\n\n# === INITIATOR SIDE ===\ninitiator = NoiseHandshake(\"Noise_IK_25519_ChaChaPoly_SHA256\")\ninitiator.set_as_initiator()\ninitiator.generate_static_keypair()  # Generate own static key\ninitiator.set_remote_static_public_key(responder_public)  # Know responder's key\ninitiator.initialize()\n\n# Send first message (-\u003e e, es, s, ss)\nmsg1 = initiator.write_message(b\"\")\n\n# === RESPONDER SIDE ===\nresponder = NoiseHandshake(\"Noise_IK_25519_ChaChaPoly_SHA256\")\nresponder.set_as_responder()\nresponder.set_static_keypair(responder_private, responder_public)  # Use existing keypair\nresponder.initialize()\n\n# Process first message and send response (-\u003e e, ee, se)\nresponder.read_message(msg1)\nmsg2 = responder.write_message(b\"\")\n\n# === INITIATOR SIDE (continued) ===\n# Process second message - handshake complete\ninitiator.read_message(msg2)\n\n# === CREATE TRANSPORT ===\ninit_send, init_recv = initiator.to_transport()\nresp_send, resp_recv = responder.to_transport()\n\ninit_transport = NoiseTransport(init_send, init_recv)\nresp_transport = NoiseTransport(resp_send, resp_recv)\n\n# Secure authenticated communication\nciphertext = init_transport.send(b\"Authenticated message\")\nplaintext = resp_transport.receive(ciphertext)\n```\n\n### Transport Layer Encryption\n\nAfter handshake completion, use the transport layer for ongoing encrypted communication:\n\n```python\nfrom noiseframework import NoiseTransport\n\n# After successful handshake, get cipher states\nsend_cipher, recv_cipher = handshake.to_transport()\n\n# Create transport wrapper\ntransport = NoiseTransport(send_cipher, recv_cipher)\n\n# Encrypt and send data\nciphertext = transport.send(b\"Sensitive data\")\n\n# Decrypt received data\nplaintext = transport.receive(ciphertext)\n\n# Send with associated data (authenticated but not encrypted)\nciphertext = transport.send(b\"payload\", ad=b\"metadata\")\nplaintext = transport.receive(ciphertext, ad=b\"metadata\")\n\n# Track nonces\nprint(f\"Messages sent: {transport.get_send_nonce()}\")\nprint(f\"Messages received: {transport.get_receive_nonce()}\")\n\n# Transport automatically handles:\n# - Nonce increment\n# - Authentication tags\n# - AEAD encryption/decryption\n```\n\n### Error Handling\n\nNoiseFramework provides helpful custom exceptions with actionable error messages:\n\n```python\nfrom noiseframework import NoiseHandshake, NoiseTransport\nfrom noiseframework.exceptions import (\n    NoiseError,                  # Base class - catches all framework errors\n    UnsupportedPatternError,     # Invalid pattern\n    RoleNotSetError,             # Role not set\n    AuthenticationError,         # Decryption/authentication failure\n)\n\n# Catch specific exceptions\ntry:\n    hs = NoiseHandshake(\"Invalid_Pattern\")\nexcept UnsupportedPatternError as e:\n    print(f\"Pattern error: {e}\")\n    # Output: Invalid pattern string format: 'Invalid_Pattern'.\n    #         Expected format: Noise_PATTERN_DH_CIPHER_HASH\n\n# Handle state errors\ntry:\n    hs = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\n    hs.write_message()  # Error: role not set\nexcept RoleNotSetError as e:\n    print(f\"State error: {e}\")\n    # Output: Cannot write handshake message: role not set.\n    #         Call set_as_initiator() or set_as_responder() first.\n\n# Handle authentication failures\ntry:\n    ciphertext_tampered = ciphertext[:-1] + b\"\\x00\"\n    transport.receive(ciphertext_tampered)\nexcept AuthenticationError as e:\n    print(f\"Authentication failed: {e}\")\n    # DO NOT process the message - discard it\n\n# Catch all framework errors\ntry:\n    # ... noise operations ...\nexcept NoiseError as e:\n    print(f\"Framework error: {type(e).__name__}: {e}\")\n```\n\n**Available exceptions**: `NoiseError` (base), `HandshakeError`, `RoleNotSetError`, `RoleAlreadySetError`, `WrongTurnError`, `HandshakeCompleteError`, `MissingKeyError`, `PatternError`, `UnsupportedPatternError`, `UnsupportedPrimitiveError`, `StateError`, `NoKeySetError`, `NonceOverflowError`, `InvalidKeySizeError`, `TransportError`, `AuthenticationError`, `CryptoError`, `ValidationError`, `FramingError`.\n\nSee `examples/error_handling_example.py` for comprehensive error handling examples.\n\n---\n\n### Logging\n\nNoiseFramework includes comprehensive logging support for debugging and monitoring. All major operations are logged at appropriate levels.\n\n#### Basic Logging Setup\n\n```python\nimport logging\nfrom noiseframework import NoiseHandshake, NoiseTransport\n\n# Configure logging\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s [%(levelname)-8s] %(name)s: %(message)s\"\n)\n\n# Use NoiseFramework normally - logging happens automatically\nhandshake = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\nhandshake.set_as_initiator()  # Logs: \"Role set as INITIATOR\"\nhandshake.generate_static_keypair()  # Logs: \"Generated static keypair\"\nhandshake.initialize()  # Logs: \"Handshake initialized\"\n\nmsg = handshake.write_message(b\"\")  # Logs: \"Sent handshake message 1\"\n```\n\n#### Log Levels\n\n- **DEBUG**: Detailed operations (message sizes, nonces, token processing, key operations)\n- **INFO**: Major events (role changes, handshake completion, message send/receive)\n- **WARNING**: Potential issues (nonce approaching limit)\n- **ERROR**: Failures (validation errors, authentication failures)\n\n#### Custom Logger\n\n```python\n# Create custom logger with specific configuration\ncustom_logger = logging.getLogger(\"myapp.noise\")\ncustom_logger.setLevel(logging.DEBUG)\n\n# Add custom handler\nhandler = logging.StreamHandler()\nhandler.setFormatter(logging.Formatter(\"%(levelname)s: %(message)s\"))\ncustom_logger.addHandler(handler)\n\n# Pass custom logger to NoiseHandshake\nhandshake = NoiseHandshake(\n    \"Noise_XX_25519_ChaChaPoly_SHA256\",\n    logger=custom_logger\n)\n\n# Pass custom logger to NoiseTransport\ntransport = NoiseTransport(send_cipher, recv_cipher, logger=custom_logger)\n```\n\n#### Filtering Logs by Module\n\n```python\n# Only show INFO+ logs from handshake module\nlogging.getLogger(\"noiseframework.noise.handshake\").setLevel(logging.INFO)\n\n# Show DEBUG logs from transport module\nlogging.getLogger(\"noiseframework.transport.transport\").setLevel(logging.DEBUG)\n\n# Disable logs from state module\nlogging.getLogger(\"noiseframework.noise.state\").setLevel(logging.WARNING)\n```\n\n#### Example Log Output\n\n```\n2025-11-24 15:30:01 [INFO    ] noiseframework.noise.handshake.NoiseHandshake: Role set as INITIATOR\n2025-11-24 15:30:01 [DEBUG   ] noiseframework.noise.handshake.NoiseHandshake: Generating static keypair (Curve25519)\n2025-11-24 15:30:01 [INFO    ] noiseframework.noise.handshake.NoiseHandshake: Generated static keypair\n2025-11-24 15:30:01 [INFO    ] noiseframework.noise.handshake.NoiseHandshake: Handshake initialized\n2025-11-24 15:30:01 [DEBUG   ] noiseframework.noise.handshake.NoiseHandshake: Writing handshake message 1 (payload=0 bytes)\n2025-11-24 15:30:01 [INFO    ] noiseframework.noise.handshake.NoiseHandshake: Sent handshake message 1 (ciphertext=32 bytes)\n2025-11-24 15:30:02 [INFO    ] noiseframework.noise.handshake.NoiseHandshake: Handshake complete - ready for transport mode\n2025-11-24 15:30:02 [INFO    ] noiseframework.noise.handshake.NoiseHandshake: Created transport ciphers (initiator: send=c1, receive=c2)\n2025-11-24 15:30:03 [INFO    ] noiseframework.transport.transport.NoiseTransport: Sent encrypted message (ciphertext=29 bytes)\n2025-11-24 15:30:03 [WARNING ] noiseframework.transport.transport.NoiseTransport: Send cipher nonce high: 9223372036854775808 (approaching 2^64 limit - consider rekeying)\n```\n\nSee [`examples/logging_example.py`](examples/logging_example.py) for more detailed examples.\n\n### Message Framing\n\nWhen 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.\n\n#### Basic Usage\n\n```python\nimport socket\nfrom noiseframework import NoiseHandshake, FramedWriter, FramedReader\n\n# After handshake completes...\ntransport = handshake.to_transport()\n\n# Wrap socket for framed communication\nwriter = FramedWriter(sock.makefile('wb'))\nreader = FramedReader(sock.makefile('rb'))\n\n# Send framed encrypted messages\nciphertext = transport.send(b\"Hello, World!\")\nwriter.write_message(ciphertext)\n\n# Receive framed encrypted messages\nframed_message = reader.read_message()\nplaintext = transport.receive(framed_message)\n```\n\n#### Frame Format\n\nNoiseFramework uses a simple 4-byte big-endian length prefix:\n\n```\n┌──────────────┬────────────────────┐\n│ Length (4B)  │  Message Data      │\n│ big-endian   │  (0 to 2^32-1 B)   │\n└──────────────┴────────────────────┘\n```\n\n#### Configuration\n\n```python\n# Default maximum message size is 16 MB\nreader = FramedReader(stream)  # max_message_size=16*1024*1024\n\n# Set custom maximum\nreader = FramedReader(stream, max_message_size=1024*1024)  # 1 MB limit\n\n# Add logging\nimport logging\nlogger = logging.getLogger(\"myapp.framing\")\nreader = FramedReader(stream, logger=logger)\nwriter = FramedWriter(stream, logger=logger)\n```\n\n#### TCP Example\n\n```python\nimport socket\nfrom noiseframework import NoiseHandshake, FramedWriter, FramedReader\n\n# Server\ndef server(port):\n    with socket.socket() as sock:\n        sock.bind(('localhost', port))\n        sock.listen(1)\n        conn, _ = sock.accept()\n        \n        # Noise handshake (responder)\n        hs = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\n        hs.set_as_responder()\n        hs.generate_static_keypair()\n        hs.initialize()\n        \n        reader = FramedReader(conn.makefile('rb'))\n        writer = FramedWriter(conn.makefile('wb'))\n        \n        # Handshake messages with framing\n        msg1 = reader.read_message()\n        msg2 = hs.read_message(msg1)\n        msg2_out = hs.write_message(msg2)\n        writer.write_message(msg2_out)\n        \n        msg3 = reader.read_message()\n        hs.read_message(msg3)\n        \n        # Transport mode\n        transport = hs.to_transport()\n        encrypted = reader.read_message()\n        plaintext = transport.receive(encrypted)\n        print(f\"Server received: {plaintext}\")\n\n# Client\ndef client(port):\n    with socket.socket() as sock:\n        sock.connect(('localhost', port))\n        \n        # Noise handshake (initiator)\n        hs = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\n        hs.set_as_initiator()\n        hs.generate_static_keypair()\n        hs.initialize()\n        \n        reader = FramedReader(sock.makefile('rb'))\n        writer = FramedWriter(sock.makefile('wb'))\n        \n        # Handshake messages with framing\n        msg1 = hs.write_message(b\"\")\n        writer.write_message(msg1)\n        \n        msg2 = reader.read_message()\n        msg3_payload = hs.read_message(msg2)\n        msg3 = hs.write_message(msg3_payload)\n        writer.write_message(msg3)\n        \n        # Transport mode\n        transport = hs.to_transport()\n        ciphertext = transport.send(b\"Hello, Server!\")\n        writer.write_message(ciphertext)\n```\n\n#### Convenience Functions\n\nFor single-message operations:\n\n```python\nfrom noiseframework import write_framed_message, read_framed_message\n\n# Write single framed message\nwrite_framed_message(stream, b\"Hello\")\n\n# Read single framed message\nmessage = read_framed_message(stream)\n\n# With custom max size\nmessage = read_framed_message(stream, max_message_size=1024*1024)\n```\n\n#### Error Handling\n\n```python\nfrom noiseframework import FramingError\n\ntry:\n    message = reader.read_message()\nexcept FramingError as e:\n    # Connection closed, oversized message, or invalid frame\n    print(f\"Framing error: {e}\")\nexcept IOError as e:\n    # Underlying stream error\n    print(f\"IO error: {e}\")\n```\n\n**Key Points:**\n- Automatic handling of partial reads\n- Protection against oversized messages (configurable limit)\n- Message counters for debugging (`messages_sent`, `messages_received`)\n- Thread-safe for concurrent read/write on different threads\n- Works with any byte stream (sockets, files, pipes, etc.)\n\nSee [`examples/framed_tcp_example.py`](examples/framed_tcp_example.py) for a complete working example.\n\n### Async/Await Support\n\nNoiseFramework 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.\n\n#### Basic Async Usage\n\n```python\nimport asyncio\nfrom noiseframework import AsyncNoiseHandshake, AsyncNoiseTransport\n\nasync def async_handshake():\n    # Create async handshake\n    handshake = AsyncNoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\n    await handshake.set_as_initiator()\n    await handshake.generate_static_keypair()\n    await handshake.initialize()\n    \n    # Perform handshake (async)\n    msg1 = await handshake.write_message(b\"\")\n    # ... exchange messages over network ...\n    \n    # Convert to async transport\n    transport = await handshake.to_transport()\n    \n    # Send/receive encrypted messages (async)\n    ciphertext = await transport.send(b\"Hello, async world!\")\n    plaintext = await transport.receive(ciphertext)\n\n# Run the async function\nasyncio.run(async_handshake())\n```\n\n#### Async TCP Server Example\n\n```python\nimport asyncio\nfrom noiseframework import (\n    AsyncNoiseHandshake,\n    AsyncFramedReader,\n    AsyncFramedWriter,\n)\n\nasync def handle_client(reader, writer):\n    \"\"\"Handle incoming client connection.\"\"\"\n    # Create responder handshake\n    handshake = AsyncNoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\n    await handshake.set_as_responder()\n    await handshake.generate_static_keypair()\n    await handshake.initialize()\n    \n    # Wrap streams with framing\n    framed_reader = AsyncFramedReader(reader)\n    framed_writer = AsyncFramedWriter(writer)\n    \n    # Perform XX handshake\n    msg1 = await framed_reader.read_message()\n    await handshake.read_message(msg1)\n    \n    msg2 = await handshake.write_message(b\"\")\n    await framed_writer.write_message(msg2)\n    \n    msg3 = await framed_reader.read_message()\n    await handshake.read_message(msg3)\n    \n    # Switch to transport mode\n    transport = await handshake.to_transport()\n    \n    # Receive and process encrypted messages\n    while True:\n        try:\n            ciphertext = await framed_reader.read_message()\n            plaintext = await transport.receive(ciphertext)\n            print(f\"Received: {plaintext.decode()}\")\n            \n            # Send encrypted response\n            response = await transport.send(b\"Message received!\")\n            await framed_writer.write_message(response)\n        except:\n            break\n    \n    await framed_writer.close()\n\nasync def main():\n    server = await asyncio.start_server(\n        handle_client, '127.0.0.1', 9999\n    )\n    async with server:\n        await server.serve_forever()\n\nasyncio.run(main())\n```\n\n#### Async TCP Client Example\n\n```python\nasync def async_client():\n    # Connect to server\n    reader, writer = await asyncio.open_connection('127.0.0.1', 9999)\n    \n    # Create initiator handshake\n    handshake = AsyncNoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\n    await handshake.set_as_initiator()\n    await handshake.generate_static_keypair()\n    await handshake.initialize()\n    \n    # Wrap streams with framing\n    framed_reader = AsyncFramedReader(reader)\n    framed_writer = AsyncFramedWriter(writer)\n    \n    # Perform XX handshake (3 messages)\n    msg1 = await handshake.write_message(b\"\")\n    await framed_writer.write_message(msg1)\n    \n    msg2 = await framed_reader.read_message()\n    await handshake.read_message(msg2)\n    \n    msg3 = await handshake.write_message(b\"\")\n    await framed_writer.write_message(msg3)\n    \n    # Switch to transport mode\n    transport = await handshake.to_transport()\n    \n    # Send encrypted messages\n    for i in range(3):\n        message = f\"Message {i+1}\".encode()\n        ciphertext = await transport.send(message)\n        await framed_writer.write_message(ciphertext)\n        \n        response_ct = await framed_reader.read_message()\n        response = await transport.receive(response_ct)\n        print(f\"Server response: {response.decode()}\")\n    \n    await framed_writer.close()\n\nasyncio.run(async_client())\n```\n\n#### Async Framing\n\nFor asyncio streams, use `AsyncFramedReader` and `AsyncFramedWriter`:\n\n```python\nfrom noiseframework import AsyncFramedReader, AsyncFramedWriter\n\nasync def async_framed_communication(reader, writer):\n    framed_writer = AsyncFramedWriter(writer)\n    framed_reader = AsyncFramedReader(reader)\n    \n    # Write framed message\n    await framed_writer.write_message(b\"Hello, async!\")\n    \n    # Read framed message\n    message = await framed_reader.read_message()\n    \n    # Close when done\n    await framed_writer.close()\n```\n\n#### Async Convenience Functions\n\n```python\nfrom noiseframework import (\n    async_write_framed_message,\n    async_read_framed_message,\n)\n\n# Write single message\nawait async_write_framed_message(writer, b\"Hello\")\n\n# Read single message\nmessage = await async_read_framed_message(reader)\n```\n\n#### Async API Classes\n\n**AsyncNoiseHandshake**: Async wrapper for `NoiseHandshake`\n- `await set_as_initiator()` - Set role as initiator\n- `await set_as_responder()` - Set role as responder\n- `await generate_static_keypair()` - Generate static keys\n- `await set_static_keypair(private, public)` - Set existing keys\n- `await set_remote_static_public_key(public)` - Set remote's public key\n- `await initialize()` - Initialize handshake state\n- `await write_message(payload)` - Write handshake message\n- `await read_message(message)` - Read handshake message\n- `await to_transport()` - Get AsyncNoiseTransport after completion\n- `await get_handshake_hash()` - Get handshake hash\n- `.is_complete` - Check if handshake is complete (property)\n\n**AsyncNoiseTransport**: Async wrapper for `NoiseTransport`\n- `await send(plaintext, ad=b\"\")` - Encrypt and send message\n- `await receive(ciphertext, ad=b\"\")` - Decrypt and receive message\n- `.send_nonce` - Current send nonce (property)\n- `.receive_nonce` - Current receive nonce (property)\n\n**AsyncFramedReader**: Async framed message reader\n- `await read_message()` - Read length-prefixed message\n- `await close()` - Close reader\n- `.messages_received` - Message counter (property)\n\n**AsyncFramedWriter**: Async framed message writer\n- `await write_message(message)` - Write length-prefixed message\n- `await close()` - Close writer and wait for completion\n- `.messages_sent` - Message counter (property)\n\n**Key Points:**\n- All async operations use `run_in_executor` internally\n- No blocking calls in the async event loop\n- Compatible with `asyncio.StreamReader` and `asyncio.StreamWriter`\n- Same security guarantees as synchronous version\n- Logging support in all async classes\n\nSee [`examples/async_tcp_example.py`](examples/async_tcp_example.py) for a complete working example with server and client.\n\n---\n\n### Pre-Shared Key (PSK) Support\n\nNoiseFramework 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.\n\n#### What are PSK Patterns?\n\nPSK patterns combine traditional Noise handshakes with pre-shared keys:\n- **Quantum Resistance**: PSKs are immune to quantum computer attacks (unlike DH)\n- **Additional Authentication**: Extra authentication layer beyond public keys\n- **Pre-computation Resistance**: Attackers can't pre-compute attacks\n- **Common Use Cases**: IoT devices, enterprise VPNs, defense systems, embedded systems\n\n#### PSK Pattern Format\n\nPSK patterns use modifiers (`psk0` through `psk4`) that indicate when the PSK is mixed:\n\n```\nNoise_XXpsk3_25519_ChaChaPoly_SHA256  # XX with PSK after third message\nNoise_NNpsk0_25519_ChaChaPoly_SHA256  # NN with PSK before first message\nNoise_IKpsk2_448_AESGCM_BLAKE2b       # IK with PSK after second message\n```\n\n**PSK Modifiers:**\n- `psk0`: PSK mixed before first message (maximum quantum resistance)\n- `psk1`: PSK mixed after first message\n- `psk2`: PSK mixed after second message (common for IK patterns)\n- `psk3`: PSK mixed after third message (most common - XXpsk3)\n- `psk4`: PSK mixed after fourth message (rare)\n\n#### Basic PSK Usage\n\n```python\nimport os\nfrom noiseframework import NoiseHandshake, NoiseTransport\n\n# Generate or load a 32-byte pre-shared key\npsk = os.urandom(32)  # Must be exchanged securely out-of-band\n\n# Initiator\ninitiator = NoiseHandshake(\"Noise_NNpsk0_25519_ChaChaPoly_SHA256\")\ninitiator.set_as_initiator()\ninitiator.set_psk(psk)  # Set PSK before initialize()\ninitiator.initialize()\n\n# Responder\nresponder = NoiseHandshake(\"Noise_NNpsk0_25519_ChaChaPoly_SHA256\")\nresponder.set_as_responder()\nresponder.set_psk(psk)  # Must use same PSK\nresponder.initialize()\n\n# Perform handshake (PSK mixed automatically)\nmsg1 = initiator.write_message(b\"\")\nresponder.read_message(msg1)\n\nmsg2 = responder.write_message(b\"\")\ninitiator.read_message(msg2)\n\n# Create transport - now quantum-resistant!\ninit_send, init_recv = initiator.to_transport()\nresp_send, resp_recv = responder.to_transport()\n\ninit_transport = NoiseTransport(init_send, init_recv)\nresp_transport = NoiseTransport(resp_send, resp_recv)\n\n# Secure communication\nciphertext = init_transport.send(b\"Quantum-resistant message!\")\nplaintext = resp_transport.receive(ciphertext)\n```\n\n#### XXpsk3 - Most Common PSK Pattern\n\nThe `XXpsk3` pattern (mutual authentication + late PSK) is the most commonly used PSK pattern:\n\n```python\npsk = os.urandom(32)\n\n# Initiator\ninitiator = NoiseHandshake(\"Noise_XXpsk3_25519_ChaChaPoly_SHA256\")\ninitiator.set_as_initiator()\ninitiator.generate_static_keypair()  # XX requires static keys\ninitiator.set_psk(psk)\ninitiator.initialize()\n\n# Responder\nresponder = NoiseHandshake(\"Noise_XXpsk3_25519_ChaChaPoly_SHA256\")\nresponder.set_as_responder()\nresponder.generate_static_keypair()\nresponder.set_psk(psk)\nresponder.initialize()\n\n# Three-message handshake\nmsg1 = initiator.write_message(b\"\")\nresponder.read_message(msg1)\n\nmsg2 = responder.write_message(b\"\")\ninitiator.read_message(msg2)\n\nmsg3 = initiator.write_message(b\"\")  # PSK mixed here\nresponder.read_message(msg3)\n\n# Both parties mutually authenticated + quantum-resistant\nassert initiator.remote_static_public == responder.static_public\nassert responder.remote_static_public == initiator.static_public\n```\n\n#### PSK with Async/Await\n\nPSK works seamlessly with async code:\n\n```python\nasync def async_psk_example():\n    psk = os.urandom(32)\n    \n    handshake = AsyncNoiseHandshake(\"Noise_XXpsk3_25519_ChaChaPoly_SHA256\")\n    await handshake.set_as_initiator()\n    await handshake.generate_static_keypair()\n    await handshake.set_psk(psk)  # Async PSK setting\n    await handshake.initialize()\n    \n    # Continue with async handshake...\n```\n\n#### PSK Security Considerations\n\n**When to Use PSK:**\n- You need quantum resistance\n- You can securely exchange a shared secret out-of-band\n- You're building IoT or embedded systems\n- You want additional authentication beyond public keys\n\n**PSK Management:**\n- PSKs must be exchanged securely (never over an insecure channel)\n- Use strong randomness (32 bytes from `os.urandom()`)\n- PSKs can be safely reused across sessions\n- Consider key rotation policies for long-lived PSKs\n\n**Placement Trade-offs:**\n- **Early PSK (psk0)**: Maximum quantum resistance, protects entire handshake\n- **Late PSK (psk3)**: Allows public key exchange first, then adds PSK protection\n\nSee [`examples/psk_example.py`](examples/psk_example.py) for complete working examples of NNpsk0, XXpsk3, and IKpsk2 patterns.\n\n---\n\n### Fallback Pattern Support\n\nNoiseFramework 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).\n\n#### What are Fallback Patterns?\n\nFallback patterns enable recovery from handshake failures without dropping the connection:\n- **Graceful Degradation**: When IK/NK fails, fallback to XX pattern\n- **Key Rotation**: Handle responder static key changes\n- **PSK Outdated**: Recover from outdated pre-shared keys\n- **Role Reversal**: Responder becomes effective initiator in fallback\n\n#### Noise Pipes Protocol (IK → XXfallback)\n\nThe most common fallback scenario is **Noise Pipes**: Alice attempts IK, Bob detects wrong static key and falls back to XXfallback.\n\n```python\nimport os\nfrom noiseframework import NoiseHandshake\n\n# Bob generates his real keys\nbob = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\nbob.set_as_responder()\nbob.generate_static_keypair()\nbob.initialize()\n\n# Alice attempts IK with WRONG Bob static key (outdated)\nalice = NoiseHandshake(\"Noise_IK_25519_ChaChaPoly_SHA256\")\nalice.set_as_initiator()\nalice.generate_static_keypair()\nalice.set_remote_static_public_key(wrong_bob_key)  # Outdated key\nalice.initialize()\n\n# Alice sends IK first message - will fail decryption\nik_msg1 = alice.write_message(b\"Hello Bob\")\n\n# Bob cannot decrypt - extract Alice's ephemeral key (first 32 bytes)\nalice_ephemeral = ik_msg1[:32]\n\n# Bob initiates fallback to XXfallback\nbob.start_fallback(alice_ephemeral)\n\n# Bob now sends first XXfallback message (role reversal)\nfallback_msg1 = bob.write_message(b\"Fallback initiated\")\n\n# Alice detects IK failure and switches to XXfallback\nalice_fallback = NoiseHandshake(\"Noise_XXfallback_25519_ChaChaPoly_SHA256\")\nalice_fallback.set_as_initiator()\nalice_fallback.set_static_keypair(alice.static_private, alice.static_public)\n\n# Reuse Alice's ephemeral keys (XXfallback uses them as pre-message)\nalice_fallback.ephemeral_private = alice.ephemeral_private\nalice_fallback.ephemeral_public = alice.ephemeral_public\nalice_fallback.initialize()\n\n# Alice reads Bob's fallback message\npayload1 = alice_fallback.read_message(fallback_msg1)\n\n# Alice sends her static key (second XXfallback message)\nfallback_msg2 = alice_fallback.write_message(b\"Acknowledged\")\n\n# Bob reads Alice's response - handshake complete!\npayload2 = bob.read_message(fallback_msg2)\n\n# Create transport channels\nbob_send, bob_recv = bob.to_transport()\nalice_send, alice_recv = alice_fallback.to_transport()\n\n# Secure communication established despite initial IK failure!\n```\n\n#### Fallback Mechanics\n\n**Pattern Transformation:**\n```\nXX → XXfallback    # First message \"e\" becomes pre-message\nNN → NNfallback    # First message \"e\" becomes pre-message\nIK → XXfallback    # Cannot use IKfallback (first message contains DH)\nNK → XXfallback    # Cannot use NKfallback (first message contains DH)\n```\n\n**Valid Fallback Patterns:**\n- Fallback requires initiator's first message to be \"e\", \"s\", or \"e, s\"\n- Cannot fallback patterns where first message contains DH operations (es, se, ss, ee)\n\n**Fallback Process:**\n1. Responder receives initiator's first message but cannot decrypt\n2. Responder extracts initiator's ephemeral key from failed message\n3. Responder calls `start_fallback(remote_ephemeral_public_key)`\n4. Pattern switches (e.g., XX → XXfallback), state re-initialized\n5. Responder sends first message (role reversal)\n6. Initiator also switches to fallback pattern\n7. Handshake completes normally with fallback pattern\n\n#### API Usage\n\n```python\n# Responder only - call when decryption fails\nhandshake.start_fallback(remote_ephemeral_public_key: bytes) -\u003e None\n\n# Async version\nawait handshake.start_fallback(remote_ephemeral_public_key: bytes)\n\n# Example error handling with fallback\ntry:\n    payload = responder.read_message(initiator_msg1)\nexcept Exception:\n    # Extract ephemeral key (first DHLEN bytes)\n    alice_ephemeral = initiator_msg1[:32]  # 32 for Curve25519\n    responder.start_fallback(alice_ephemeral)\n    # Continue with fallback pattern...\n```\n\n#### Security Considerations\n\n**When to Use Fallback:**\n- Server static key rotation scenarios\n- PSK may be outdated but fallback acceptable\n- Graceful degradation preferred over connection failure\n\n**Fallback Limitations:**\n- Both parties must coordinate fallback (initiator must also switch)\n- Ephemeral key must be preserved from failed message\n- Only works with patterns where first message can be pre-message\n\n**Noise Pipes Use Case:**\nThe IK → XXfallback pattern is standardized as \"Noise Pipes\" and widely used for:\n- IoT device provisioning (device has outdated server key)\n- Server key rotation (clients gradually update)\n- Fallback from authenticated to mutual authentication\n\nSee [`examples/fallback_example.py`](examples/fallback_example.py) for a complete working demonstration of the Noise Pipes protocol with IK → XXfallback.\n\n---\n\n### High-Level Connection API\n\nFor 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.\n\n#### Synchronous Connection Example\n\n```python\nfrom noiseframework import NoiseConnection\nimport socket\nimport threading\n\ndef server():\n    \"\"\"Responder side.\"\"\"\n    server_sock = socket.socket()\n    server_sock.bind((\"localhost\", 9999))\n    server_sock.listen(1)\n    client_sock, _ = server_sock.accept()\n    \n    # Create connection and accept - handshake happens automatically\n    with NoiseConnection(\"Noise_XX_25519_ChaChaPoly_SHA256\", \"responder\") as conn:\n        conn.accept(client_sock)\n        \n        # Now in transport mode - send/receive encrypted messages\n        data = conn.receive()\n        conn.send(b\"Echo: \" + data)\n    \n    server_sock.close()\n\n# Start server in background\nthreading.Thread(target=server, daemon=True).start()\n\n# Client (initiator side)\nwith NoiseConnection(\"Noise_XX_25519_ChaChaPoly_SHA256\", \"initiator\") as conn:\n    conn.connect((\"localhost\", 9999))  # Handshake happens automatically\n    \n    conn.send(b\"Hello, NoiseFramework!\")\n    response = conn.receive()\n    print(response)  # b\"Echo: Hello, NoiseFramework!\"\n```\n\n#### Asynchronous Connection Example\n\n```python\nimport asyncio\nfrom noiseframework import AsyncNoiseConnection\n\nasync def handle_client(reader, writer):\n    \"\"\"Async responder.\"\"\"\n    async with AsyncNoiseConnection(\"Noise_XX_25519_ChaChaPoly_SHA256\", \"responder\") as conn:\n        await conn.accept_streams(reader, writer)  # Auto handshake\n        \n        data = await conn.receive()\n        await conn.send(b\"Echo: \" + data)\n\nasync def main():\n    # Start async server\n    server = await asyncio.start_server(handle_client, \"localhost\", 9999)\n    \n    # Client\n    async with AsyncNoiseConnection(\"Noise_XX_25519_ChaChaPoly_SHA256\", \"initiator\") as conn:\n        await conn.connect((\"localhost\", 9999))  # Auto handshake\n        \n        await conn.send(b\"Hello, async!\")\n        response = await conn.receive()\n        print(response)  # b\"Echo: Hello, async!\"\n    \n    server.close()\n    await server.wait_closed()\n\nasyncio.run(main())\n```\n\n#### Connection API Features\n\n**NoiseConnection** (sync) and **AsyncNoiseConnection** (async):\n\n```python\n# Constructor\nconn = NoiseConnection(\n    pattern=\"Noise_XX_25519_ChaChaPoly_SHA256\",\n    role=\"initiator\",  # or \"responder\"\n    static_private_key=None,  # Optional: use custom keys\n    static_public_key=None,\n    remote_static_public_key=None,  # For IK/NK patterns\n    max_message_size=16*1024*1024,  # 16 MB default\n    logger=None  # Optional logging\n)\n\n# Initiator methods\nconn.connect((\"host\", port))  # Sync\nawait conn.connect((\"host\", port))  # Async\n\n# Responder methods\nconn.accept(client_socket)  # Sync\nawait conn.accept_streams(reader, writer)  # Async\n\n# Communication (same for both roles)\nconn.send(plaintext_bytes)  # Sync\nawait conn.send(plaintext_bytes)  # Async\n\nplaintext = conn.receive()  # Sync\nplaintext = await conn.receive()  # Async\n\n# Connection state\nif conn.is_connected:\n    print(\"Connected and handshake complete\")\n\n# Identity verification\nremote_key = conn.remote_static_public_key  # Get remote's identity\nlocal_key = conn.local_static_public_key    # Get our identity\n\n# Cleanup\nconn.close()  # Sync\nawait conn.close()  # Async\n\n# Or use context managers (automatic cleanup)\nwith NoiseConnection(...) as conn:\n    # ... use conn ...\n# Closes automatically\n\nasync with AsyncNoiseConnection(...) as conn:\n    # ... use conn ...\n# Closes automatically\n```\n\n#### Benefits of Connection API\n\n- ✅ **Automatic handshake** - No manual `write_message()`/`read_message()` calls\n- ✅ **Automatic framing** - Built-in length-prefixed message framing\n- ✅ **Automatic transport** - Seamless transition from handshake to encryption\n- ✅ **Simple lifecycle** - Just `connect()`/`accept()`, `send()`, `receive()`, `close()`\n- ✅ **Identity access** - Easy access to local and remote public keys\n- ✅ **Context managers** - Automatic cleanup with `with` statements\n- ✅ **Clear errors** - ValidationError, HandshakeError, TransportError\n\n#### When to Use Each API\n\n**Use `NoiseConnection`/`AsyncNoiseConnection` when:**\n- Building complete secure connections (most common use case)\n- You want simplicity and automatic handling\n- Standard patterns (XX, IK, NK, etc.) are sufficient\n\n**Use `NoiseHandshake` + `NoiseTransport` when:**\n- You need fine control over handshake steps\n- Implementing custom protocols on top of Noise\n- Building non-standard integrations\n\nSee [`examples/connection_example.py`](examples/connection_example.py) for complete examples including custom keys and identity verification.\n\n---\n\n## 🖥️ CLI Documentation\n\nThe `NoiseFramework` command-line tool provides easy access to key operations without writing code.\n\n### Generate Keypair\n\nGenerate static keypairs for use in Noise handshakes:\n\n```bash\n# Generate Curve25519 keypair (default)\nnoiseframework generate-keypair -o mykey\n# Creates: mykey_private.key (32 bytes), mykey_public.key (32 bytes)\n\n# Generate Curve448 keypair\nnoiseframework generate-keypair --dh 448 -o mykey448\n# Creates: mykey448_private.key (56 bytes), mykey448_public.key (56 bytes)\n\n# Use short alias\nnoiseframework genkey -o server_key\n```\n\n**Output:**\n```\nGenerated keypair:\n  Private key: mykey_private.key\n  Public key:  mykey_public.key\n  Key size:    32 bytes\n```\n\n**Usage in Python:**\n```python\nfrom pathlib import Path\nfrom noiseframework import NoiseHandshake\n\n# Load generated keys\nprivate_key = Path(\"mykey_private.key\").read_bytes()\npublic_key = Path(\"mykey_public.key\").read_bytes()\n\n# Use in handshake\nhs = NoiseHandshake(\"Noise_XX_25519_ChaChaPoly_SHA256\")\nhs.set_static_keypair(private_key, public_key)\n```\n\n### Validate Pattern\n\nValidate Noise pattern strings and view their components:\n\n```bash\n# Validate a pattern\nnoiseframework validate-pattern \"Noise_XX_25519_ChaChaPoly_SHA256\"\n\n# Use short alias\nnoiseframework validate \"Noise_IK_448_AESGCM_BLAKE2b\"\n```\n\n**Output:**\n```\nPattern: Noise_XX_25519_ChaChaPoly_SHA256\n  Valid: ✓\n  Name:       Noise_XX_25519_ChaChaPoly_SHA256\n  Handshake:  XX\n  DH:         25519\n  Cipher:     ChaChaPoly\n  Hash:       SHA256\n```\n\n**Invalid pattern:**\n```bash\nnoiseframework validate \"Noise_INVALID_Pattern\"\n# Error: Invalid pattern: Unsupported handshake pattern: INVALID\n```\n\n### Show Information\n\nDisplay supported cryptographic primitives and patterns:\n\n```bash\nnoiseframework info\n```\n\n**Output:**\n```\nNoiseFramework - Noise Protocol Framework Implementation\n\nSupported DH functions:\n  - 25519 (Curve25519/X25519)\n  - 448 (Curve448/X448)\n\nSupported ciphers:\n  - ChaChaPoly (ChaCha20-Poly1305) [recommended]\n  - AESGCM (AES-256-GCM)\n\nSupported hash functions:\n  - SHA256 [recommended]\n  - SHA512\n  - BLAKE2s\n  - BLAKE2b\n\nSupported patterns:\n  NN, NK, NX, KN, KK, KX, XN, XK, XX, IN, IK, IX\n\nExample pattern string:\n  Noise_XX_25519_ChaChaPoly_SHA256\n```\n\n### Help and Version\n\n```bash\n# Show help\nnoiseframework --help\nnoiseframework generate-keypair --help\n\n# Show version\nnoiseframework --version\n```\n\n---\n\n## 🔐 Supported Patterns\n\nNoiseFramework supports all fundamental and interactive Noise patterns:\n\n| Pattern | Description | Use Case |\n|---------|-------------|----------|\n| `NN` | No static keys | Anonymous communication |\n| `KN` | Initiator known | Server authentication |\n| `NK` | Responder known | Client knows server's key |\n| `KK` | Both known | Pre-shared public keys |\n| `NX` | Responder transmits | Certificate-like exchange |\n| `KX` | Initiator known, responder transmits | Hybrid authentication |\n| `XN` | Initiator transmits | Basic server setup |\n| `IN` | Initiator identity hidden | Privacy-preserving |\n| `XK` | Responder known, initiator transmits | Standard mutual auth |\n| `IK` | Responder known, initiator identity hidden | Tor-like handshake |\n| `XX` | Both transmit | Full mutual authentication |\n| `IX` | Initiator identity hidden, responder transmits | Privacy + auth |\n\n### Pattern Modifiers\n\n- **`psk0`, `psk1`, `psk2`**: Pre-shared symmetric key modes\n- **Fallback patterns**: For retry and downgrade scenarios\n\n---\n\n## 🔑 Cryptographic Primitives\n\nNoiseFramework uses battle-tested cryptographic libraries:\n\n### Diffie-Hellman Functions\n- **Curve25519** (X25519) - Recommended\n- **Curve448** (X448)\n\n### Cipher Functions (AEAD)\n- **ChaChaPoly** (ChaCha20-Poly1305) - Recommended\n- **AESGCM** (AES-256-GCM)\n\n### Hash Functions\n- **SHA-256** - Recommended\n- **SHA-512**\n- **BLAKE2s**\n- **BLAKE2b**\n\n**Example pattern string**: `Noise_XX_25519_ChaChaPoly_SHA256`\n\nFormat: `Noise_[PATTERN]_[DH]_[CIPHER]_[HASH]`\n\n---\n\n## 🏗️ Architecture\n\n```\nnoiseframework/\n├── noiseframework/\n│   ├── __init__.py          # Public API\n│   ├── exceptions.py        # Custom exception hierarchy\n│   ├── noise/\n│   │   ├── handshake.py     # Handshake state machine\n│   │   ├── pattern.py       # Pattern parser and validator\n│   │   └── state.py         # Cipher and symmetric state\n│   ├── crypto/\n│   │   ├── dh.py            # Diffie-Hellman functions\n│   │   ├── cipher.py        # AEAD cipher implementations\n│   │   └── hash.py          # Hash function wrappers\n│   ├── transport/\n│   │   └── transport.py     # Post-handshake encryption\n│   ├── framing.py           # Message framing utilities\n│   ├── async_support.py     # Async/await wrappers\n│   └── cli/\n│       └── main.py          # Command-line interface\n├── tests/\n│   ├── test_handshake.py\n│   ├── test_transport.py\n│   ├── test_pattern.py\n│   ├── test_cipher.py\n│   ├── test_exceptions.py   # Exception tests\n│   ├── test_logging.py      # Logging tests\n│   ├── test_framing.py      # Framing tests\n│   └── test_async.py        # Async tests\n├── examples/\n│   ├── basic_client_server.py\n│   ├── simple_chat.py\n│   ├── file_encryption.py\n│   ├── error_handling_example.py\n│   ├── logging_example.py\n│   ├── framed_tcp_example.py\n│   └── async_tcp_example.py\n├── docs/\n│   ├── API.md\n│   ├── CHANGELOG.md\n│   ├── ARCHITECTURE.md\n│   ├── SECURITY.md\n│   └── ...\n├── pyproject.toml\n└── README.md\n```\n\n---\n\n## 🧪 Testing\n\nNoiseFramework has comprehensive test coverage with **311 tests** achieving **100% pass rate**.\n\n### Test Categories\n- **Core functionality** (156 tests): Handshake, transport, patterns, crypto primitives\n- **Exception handling** (15 tests): Custom exception hierarchy and error messages\n- **Logging** (21 tests): Logging functionality across all components\n- **Framing** (30 tests): Message framing for stream-based transports\n- **Async support** (21 tests): Async/await functionality\n\n---\n\n## ⚡ Performance\n\nNoiseFramework delivers production-ready performance with real-world benchmarks:\n\n- **Handshakes**: ~1,500-1,800 complete handshakes/sec (XX pattern)\n- **Transport encryption**: **3+ GB/s** throughput for large messages\n- **Key generation**: ~32,000 keypairs/sec (Curve25519)\n- **Latency**: \u003c3 µs per small message encryption\n\n### Quick Benchmark Results\n\n| Operation | Performance |\n|-----------|-------------|\n| Complete XX handshake | 558-642 µs |\n| Encrypt 64 KB message | 18.5 µs (3.29 GB/s) |\n| Encrypt 1 KB message | 2.4 µs (403 MB/s) |\n| Generate Curve25519 keypair | 31 µs |\n\n**See [BENCHMARKS.md](docs/BENCHMARKS.md) for comprehensive performance analysis, methodology, and optimization tips.**\n\n### Run Benchmarks Yourself\n\n```bash\n# Activate virtual environment\nsource .venv/bin/activate  # Linux/macOS\n# or\n.\\.venv\\Scripts\\Activate.ps1  # Windows PowerShell\n\n# Run benchmark script\npython benchmark.py\n```\n\n---\n\n## 🧪 Testing (Detailed)\n\nRun the test suite:\n\n```bash\n# Install test dependencies\npip install -e \".[dev]\"\n\n# Run all tests\npytest\n\n# Run with coverage\npytest --cov=noiseframework --cov-report=html\n\n# Run specific test file\npytest tests/test_handshake.py\n\n# Run with verbose output\npytest -v\n```\n\n### Test Categories\n\n- **Unit tests**: Test individual components in isolation\n- **Integration tests**: Test complete handshake flows\n- **Property-based tests**: Use Hypothesis for invariant testing\n- **Vector tests**: Validate against official Noise test vectors\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please follow these guidelines:\n\n1. **Fork the repository** and create a feature branch\n2. **Follow the coding style**: PEP 8, type hints, and existing conventions\n3. **Write tests**: All new features must include tests\n4. **Update documentation**: Add examples and update `CHANGELOG.md`\n5. **Run the test suite**: Ensure all tests pass\n6. **Submit a pull request**: Describe your changes clearly\n\nSee [CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed guidelines.\n\n### Development Setup\n\n```bash\ngit clone https://github.com/juliuspleunes4/noiseframework.git\ncd noiseframework\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\npip install -e \".[dev]\"\n```\n\n---\n\n## ❓ FAQ\n\n### Which pattern should I use?\n\n- **XX**: Default choice for mutual authentication\n- **NN**: Quick anonymous encryption (no authentication)\n- **IK**: When client knows server's key in advance (like Tor)\n- **NK**: When server identity is public (like HTTPS with pinning)\n\n### Is NoiseFramework production-ready?\n\nYes, but with caveats:\n- ✅ Cryptographically sound (uses battle-tested primitives)\n- ✅ Specification-compliant implementation\n- ✅ Well-tested (311 tests, 100% pass rate)\n- ✅ Comprehensive error handling with helpful messages\n- ✅ Production-ready logging and monitoring support\n- ⚠️ Consider security audit for high-stakes applications\n- ⚠️ Keep dependencies updated\n\n### How does it compare to other Noise implementations?\n\n- **PyNaCl/libsodium**: Lower-level, NoiseFramework is higher-level Noise protocol\n- **noiseprotocol (Python)**: Similar, but NoiseFramework has better docs and CLI\n- **snow (Rust)**: Faster, but NoiseFramework is pure Python with better accessibility\n\n### Can I use custom cryptographic primitives?\n\nYes, you can extend the crypto modules. However, we strongly recommend using only well-vetted primitives from established libraries.\n\n### Does it support post-quantum cryptography?\n\nNot yet. Post-quantum Noise patterns (pqXX, etc.) are planned for future releases.\n\n---\n\n## 🔒 Security\n\n### Reporting Vulnerabilities\n\nIf you discover a security vulnerability, please **DO NOT** open a public issue. Instead:\n\n1. Email security concerns to: [jjgpleunes@gmail.com]\n2. Include a detailed description and steps to reproduce\n3. Allow reasonable time for a fix before public disclosure\n\n### Security Best Practices\n\n- **Key Management**: Never hard-code keys in source code\n- **RNG**: Use system-provided cryptographically secure random number generators\n- **Updates**: Keep NoiseFramework and its dependencies up-to-date\n- **Audit**: Consider professional security audits for production use\n- **Side-Channels**: Be aware of timing and other side-channel attacks\n\n### Dependencies\n\nNoiseFramework relies on:\n- `cryptography` - Audited, well-maintained Python cryptography library\n- No custom cryptographic primitives\n\n---\n\n## 📄 License\n\nThis project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.\n\n---\n\n## 🙏 Acknowledgments\n\n- **[Trevor Perrin](https://github.com/trevp)** - Creator of the Noise Protocol Framework\n- **Noise Protocol Community** - For the specification and test vectors\n- **PyCA Cryptography** - For providing robust cryptographic primitives\n\n---\n\n## 📚 Resources\n\n- [Noise Protocol Framework Specification](https://noiseprotocol.org/noise.html)\n- [Noise Explorer](https://noiseexplorer.com/) - Formal verification of Noise patterns\n- [Noise Wiki](https://github.com/noiseprotocol/noise_wiki/wiki)\n- [PyCA Cryptography Documentation](https://cryptography.io/)\n\n---\n\n## 🚀 Roadmap \u0026 Future Features\n\nNoiseFramework v1.3.0 is **feature-complete** with all planned production-readiness enhancements implemented. Future development is planned for v1.4.0 and beyond.\n\n### Completed in v1.3.0 ✅\n\nAll 7 major features have been implemented:\n- ✅ Async/Await Support (21 tests)\n- ✅ Message Framing (30 tests)\n- ✅ Better Error Messages (15 tests)\n- ✅ High-Level Connection API (25 tests)\n- ✅ Logging Support (21 tests)\n- ✅ PSK Support (22 tests)\n- ✅ Fallback Pattern Support (21 tests)\n\n### Planned for v1.4.0 🔄\n\nThe next version will focus on advanced Noise Protocol Framework features:\n\n1. **Rekey Support** - Prevent nonce exhaustion for long-lived connections\n   - Manual and automatic rekeying\n   - Essential for IoT, VPNs, and persistent servers\n   - Spec: Section 11.3\n\n2. **Deferred Patterns** - Support NX, KX, IX patterns\n   - Responder identity revealed later in handshake\n   - Server-side optimization opportunities\n   - Spec: Section 10.4\n\n3. **Channel Binding** - Link Noise session to application context\n   - Prevents session confusion attacks\n   - Required for some compliance scenarios\n   - Spec: Section 11.2\n\n### Future Considerations (v1.5.0+) 💡\n\n- Out-of-Order Transport (UDP support)\n- Post-Quantum Cryptography (Kyber, hybrid schemes)\n- Hardware Security Module (HSM) support\n- Additional cipher suites\n- Performance optimizations\n- Protocol plugins/extensions\n\n**📋 See [docs/TODO.md](docs/TODO.md) for detailed feature descriptions, planned APIs, and implementation roadmap.**\n\n---\n\n## 📞 Support\n\n- **Issues**: [GitHub Issues](https://github.com/juliuspleunes4/noiseframework/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/juliuspleunes4/noiseframework/discussions)\n- **Documentation**: [Full Documentation](https://noiseframework.readthedocs.io/)\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eBuilt with ❤️ for secure communications\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eIf you find this project useful, please consider giving it a ⭐️\u003c/sub\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuliuspleunes4%2Fnoiseframework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuliuspleunes4%2Fnoiseframework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuliuspleunes4%2Fnoiseframework/lists"}