>NoiseFramework
Home/Documentation/Quick Start

Quick Start Guide

Learn the basics of NoiseFramework in 5 minutes

Understanding the Noise Protocol

What is Noise?

The Noise Protocol Framework is a framework for building cryptographic protocols based on Diffie-Hellman key agreement. It provides patterns for mutual and one-way authentication, identity hiding, and strong forward secrecy.

Key concepts include handshake patterns (the sequence of messages exchanged), DH functions (for key exchange), AEAD ciphers (for encryption), and hash functions (for key derivation).

Read Official Specification

Key Components

NoiseHandshake

Manages the handshake state machine. Handles key generation, message exchange, and transitions to the transport layer.

NoiseTransport

Provides post-handshake encrypted communication. Handles message encryption, decryption, and nonce management.

Pattern String Format

Noise_<pattern>_<dh>_<cipher>_<hash>

Example: Noise_XX_25519_ChaChaPoly_SHA256

Roles

Initiator

The party that starts the handshake by sending the first message. Often the client in a client-server architecture.

Responder

The party that responds to the handshake. Often the server in a client-server architecture.

Complete XX Handshake Example

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

Step 1: Import and Setup

from noiseframework import NoiseHandshake, NoiseTransport

Step 2: Create Initiator

# Create initiator instance
initiator = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.generate_static_keypair()
initiator.initialize()

Step 3: Create Responder

# Create responder instance
responder = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.generate_static_keypair()
responder.initialize()

Step 4: Exchange Messages

# Message 1: Initiator → Responder
msg1 = initiator.write_message(b"")
responder.read_message(msg1)

# Message 2: Responder → Initiator
msg2 = responder.write_message(b"")
initiator.read_message(msg2)

# Message 3: Initiator → Responder
msg3 = initiator.write_message(b"")
responder.read_message(msg3)

Step 5: Create Transport

# Get cipher states
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)

Step 6: Send Encrypted Messages

# Initiator → Responder
ciphertext = init_transport.send(b"Secret message")
plaintext = resp_transport.receive(ciphertext)
print(plaintext.decode())  # "Secret message"

# Responder → Initiator
ciphertext = resp_transport.send(b"Response")
plaintext = init_transport.receive(ciphertext)
print(plaintext.decode())  # "Response"

What Happened?

The XX pattern exchanges three messages. The first establishes ephemeral keys, the second transmits the responder's static key, and the third transmits the initiator's static key. After completion, both parties have authenticated each other and derived shared encryption keys.

Other Handshake Patterns

NN Pattern - Anonymous Communication

No static keys required. Provides encryption without authentication. Use when you need quick encrypted channels without identity verification.

# Initiator
initiator = NoiseHandshake("Noise_NN_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.initialize()

# Responder
responder = NoiseHandshake("Noise_NN_25519_ChaChaPoly_SHA256")
responder.set_as_responder()
responder.initialize()

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

Use case: Anonymous file transfers, temporary encrypted sessions, or when authentication is handled separately.

IK Pattern - Pre-Shared Keys

Initiator knows responder's public key in advance. Provides privacy for the initiator's identity. Similar to how Tor connects to servers.

# Pre-generate responder's keypair
responder_setup = NoiseHandshake("Noise_IK_25519_ChaChaPoly_SHA256")
responder_setup.generate_static_keypair()
responder_public = responder_setup.static_public

# Initiator knows responder's public key
initiator = NoiseHandshake("Noise_IK_25519_ChaChaPoly_SHA256")
initiator.set_as_initiator()
initiator.generate_static_keypair()
initiator.set_remote_static_public_key(responder_public)
initiator.initialize()

# Faster two-message handshake
msg1 = initiator.write_message(b"")
responder.read_message(msg1)
msg2 = responder.write_message(b"")
initiator.read_message(msg2)

Use case: Client-server with public server keys, reduced latency, privacy-preserving authentication.

Using the Transport Layer

After handshake completion, use NoiseTransport for encrypted communication. The transport layer automatically handles nonce management and AEAD encryption.

Sending and Receiving Messages

# Get cipher states after handshake
send_cipher, recv_cipher = handshake.to_transport()
transport = NoiseTransport(send_cipher, recv_cipher)

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

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

Using Associated Data

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

Tracking Nonces

# Check message counters
print(f"Messages sent: {transport.get_send_nonce()}")
print(f"Messages received: {transport.get_receive_nonce()}")

Error Handling Best Practices

Common Errors

Invalid Pattern String

Raised when pattern string format is incorrect or uses unsupported primitives

Authentication Failure

Occurs when message authentication fails or keys don't match

Nonce Overflow

Transport nonce exceeds maximum value (2^64 - 1) - requires rekeying

Wrong Handshake State

Operation called at incorrect stage of handshake

Example Error Handling

try:
    handshake = NoiseHandshake("Noise_XX_25519_ChaChaPoly_SHA256")
    handshake.set_as_initiator()
    handshake.initialize()
    
    # Perform handshake...
    msg = handshake.write_message(b"")

except ValueError as e:
    print(f"Handshake error: {e}")

except Exception as e:
    print(f"Unexpected error: {e}")

Best Practices

Verify Remote Static Keys

Always validate remote public keys through out-of-band channels when possible

Choose the Right Pattern

Use XX for mutual auth, NN for anonymous, IK for pre-shared keys

Handle Nonce Overflow

Rekey or start new handshake before nonce reaches 2^64 - 1

Don't Reuse Handshakes

Create new handshake objects for each connection

Secure Key Storage

Never hard-code keys. Use secure key management systems

Use Transport Objects

Always use NoiseTransport for post-handshake communication

Next Steps

API Reference

Explore the complete API documentation

Read Docs

Examples

View real-world code examples

View Examples

Security Guidelines

Learn security best practices

Read Guide