QIAOPANG.COM · SELECTED ESSAYS (2013–2026) · 260 ESSAYSBrowse Chronologically (2013 → 2026) ↗
QiaoPang · Programmers LifeSelected Essays · Systems, Craft & Agentic Era
Timeline ↗

The Noise Framework: A Blueprint for Building Secure Protocols

The Noise Framework: A Blueprint for Building Secure Protocols

The Noise Protocol Framework (hereafter simply Noise) is a framework for building secure protocols. Unlike fully implemented protocols such as TLS and IPSec, Noise is more of a blueprint — it provides a template for developers who want to create their own secure protocols. Just as metaprogramming is to programming, Noise is the meta-protocol of protocols.

You may well ask: now that we have TLS, why would we ever need to build our own secure protocol?

It’s true that TLS 1.3, compared with its predecessors, has dropped support for a great many legacy algorithms, and the entire stack is cleaner and more “modern” — the handshake requires only 1-RTT (or even 0-RTT). These ideas align neatly with the direction Noise advocates. But using TLS inevitably drags in PKI: you need a server certificate and (optionally) a client certificate, and certificates must be issued by a CA (whether public or private). The whole stack remains quite complex, and it is very hard to apply to decentralized peer-to-peer networks.

So when we want to build private protocols directly on top of TCP or UDP, TLS is rather cumbersome and not the best choice — yet a homegrown security protocol may not be trustworthy either. Noise, then, is a safe and flexible option.

In our earlier article on WireGuard, we mentioned that the cornerstone of establishing a secure channel is the DH algorithm: the two parties negotiating the channel each hold a private random value, exchange their public values, and through a congruence-based computation independently derive the same key on both ends — thus agreeing on a shared key without ever transmitting it. An efficient and secure variant of DH is ECDH:

The Noise Framework: A Blueprint for Building Secure Protocols

If we are to develop our own secure protocol, DH/ECDH is virtually unavoidable. Once the key is negotiated, we must consider which symmetric encryption and hash algorithms to use, how to verify each party’s identity, how to guarantee forward secrecy (a leaked current key must not compromise past keys), and how to rekey in each direction of transmission (regenerating keys after a period of use), and so on. Noise lays down strict rules for all these headache-inducing problems.

A Bird’s-Eye View of the Noise Protocol

First, let’s be clear about one thing: although Noise was originally intended to provide secure channels for network protocols, it does not mandate any particular transport protocol — TCP/UDP, or indeed any subsystem satisfying a read/write interface, such as a file or a pipe, can use Noise.

The Noise Framework: A Blueprint for Building Secure Protocols — architecture diagram

Excluding appendices, the Noise specification runs to 41 pages, packed with concepts and protocol details. Before diving into the details, let’s look at a Noise-based system from the user’s — or the product’s — perspective.

First comes the protocol handshake. During the handshake, the initiator and responder (note that Noise avoids the terms Client/Server, though we can loosely think of the initiator as the client and the responder as the server) exchange messages to agree on which algorithms to use and what the keys are.

Both parties must use the same protocol variables during the handshake — unlike TLS, Noise designs the protocol variables to be static rather than negotiated. This is a major simplification, and from the user’s perspective, applications built with Noise typically have their own nodes talking to their own nodes, so no negotiation is needed. The WireGuard example we discussed last time uses these variables: Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s. A quick breakdown:

  • I: The initiator’s static public key is sent to the responder unencrypted
  • K: The initiator already knows the responder’s public key in advance
  • psk2: The Pre-Shared Key is mixed in after the second handshake message
  • ChaChaPoly: The symmetric encryption algorithm
  • BLAKE2s: The hash algorithm

The protocol variables are specified like this: Noise _ _ _ _ . There are many handshake patterns, suited to many different situations. The IK pattern’s handshake looks like this — it needs only 1-RTT, and we’ll go into detail shortly:

The Noise Framework: A Blueprint for Building Secure Protocols — architecture diagram

During the handshake, Noise creates a HandshakeState, which records the peer’s static and ephemeral public keys received so far, along with the ephemeral data computed via ECDH. When the handshake completes, both parties have symmetric information and can derive matching keys. The initiator and responder then generate key pairs for their respective receiving and sending directions.

After that, Noise lets the user switch the protocol state into Transport Mode. During the switch, HandshakeState becomes CipherState, and from then on encrypt/decrypt can be used to encrypt and decrypt application data.

In theory, Noise’s user-facing API can be just these few calls:

  • build: Initialize HandshakeState from the protocol variables and the static private key.
  • write(msg, buf): Based on the current state, compose a protocol message or encrypt the user-supplied buffer.
  • read(buf, msg): Based on the current state, read the user-supplied buffer, drive the handshake state machine, or decrypt the user-supplied buffer.
  • into_transport_mode: Convert HandshakeState into CipherState.
  • rekey: In transport mode, the user can call rekey to update keys.

For the user’s application, this API is remarkably simple and easy to wire into existing systems. From a product perspective, simplicity means not only beauty but also that it’s hard to misuse.

Handshake Details of the Noise Protocol

The core of Noise is the handshake protocol, which supports a total of 12 different negotiation patterns. These patterns can be further combined with PSK, asymmetric encryption algorithms, symmetric encryption algorithms, and hash algorithms to produce hundreds of different protocol variables. Of course, the core is those 12 patterns. Each pattern is denoted by two letters: the first letter represents the initiator, the second the responder:

The Noise Framework: A Blueprint for Building Secure Protocols

Where:

  • N: The party has no static key pair of its own
  • K: The party’s static public key is known to the peer in advance (through some other channel)
  • X: The party’s static public key is transmitted to the peer encrypted over the network (for identity hiding)
  • I: The party’s static public key is transmitted to the peer in plaintext (no identity hiding); the I state appears only on the initiator side

With these concepts, the meaning of each pattern becomes easy to grasp — for example, KX. The receiver knows the initiator’s static public key in advance, and the receiver’s static public key is transmitted to the initiator encrypted over the network. Because the receiver knows the initiator’s static public key beforehand, it means the initiator has sent its static public key s to the receiver via some channel before the handshake. This is what the -> s before the ... in the diagram means. Everything before ... represents prior transmission — it is not part of the handshake.

Next, let’s look at what characters like e, ee mean. A single character means receiving the peer’s public key in the direction of the arrow: s is a static public key, e is an ephemeral public key. Two characters represent a DH operation — the first character is one’s own public key, the second is the peer’s. Let’s walk through XK (adding an r prefix to peer keys, for remote, to keep them distinct):

  • <- s: The receiver’s static public key is provided to the sender in advance
  • ...: The preceding messages were already completed before the handshake and are prior knowledge
  • -> e, es: The sender sends its freshly generated ephemeral public key e, then performs DH(e, rs) using its own ephemeral key e and the peer’s static key s. After the receiver gets e, it can likewise compute DH(re, s).
  • <- e, ee: The receiver sends its own freshly generated ephemeral public key e, then performs DH(e, re); after the sender receives the peer’s e, it performs DH(re, e).
  • -> s, se: At this point both sides can derive an ephemeral key; the sender encrypts and sends its static public key with it, then performs DH(s, re); after the receiver gets the sender’s s, it performs DH(rs, e).
  • The negotiation is now complete

XK is the longest handshake pattern in Noise; all other patterns are 1-RTT. For a security analysis of XK (or other patterns), explore Noise Explorer — I won’t go into detail here: https://noiseexplorer.com/patterns/XK/.

Each DH step in the Noise handshake is not an isolated event. From the moment HandshakeState is created, Noise maintains a chaining key, and the result of every operation is folded into it. For how exactly the chaining key is computed during the handshake and how the state machine is maintained, refer to the Noise Spec — I won’t repeat it here.

Applications of the Noise Protocol

WireGuard is arguably the most successful public application to date. Because Noise is mainly used to strengthen the security of private protocols, many applications remain unknown to the public. According to the official WhatsApp Encryption Overview document, WhatsApp’s end-to-end message encryption uses the Noise protocol. Bitcoin’s Lightning Network also uses Noise. rust-libp2p applies Noise, which means polkadot, substrate, and Facebook’s libra also (potentially) use Noise.

In addition, Noise can be used to encrypt files — as long as I have your public key, I can use a one-way handshake (7.4) to encrypt a file (along with a message sent during the handshake) and transmit it to some insecure location (a cloud drive, FTP, IPFS, even a blockchain); only the holder of the private key can decrypt it. This is a fascinating application, because it offers the security of asymmetric encryption together with the encryption/decryption speed of symmetric encryption.

If you’re interested in the Noise protocol, the snow crate for Rust (which the author has verified), libdisco for Golang (unverified), and flynn/noise (not updated for two years) all look promising. Snow in particular is elegantly implemented and easy to combine with other modules — lower layers like tokio/async-std and higher layers like yamux. Snow is widely used in many open-source projects, especially in blockchain and IoT projects.

MORE IN Systems & Architecture

Further Reading in This Column

View all essays in “Systems & Architecture” →

The Future of Logs: Rectifying Names

For a decade we built observability into a beautiful tower on a foundation of drifting strings—field names by convention, cardinality by luck, bills by endurance. This essay proposes a radical fix: use protobuf to give every observed event a proper name, unify logs, metrics, and audit into one versioned, owner-tagged, retention-aware event catalog, and keep an escape hatch called ForensicEvent for the unknown unknowns.

New Hope (2025–2026)10,772 chars · 24 min read

Abandoning Multi-Environment Entirely: Reshaping the Next-Generation Serverless Architecture with Rust and Pingora

When we return to first principles and reconsider what "compute" really is, we find that multi-environment setups are an illusion, Kubernetes is an over-abstraction, and Serverless cold starts and connection-pool storms are self-inflicted. This essay dissects an environment-less architecture built on Baggage Header routing, a Pingora smart gateway, Firecracker microVMs, and a Rust Data Bus — an attempt to answer: what should compute look like in the post-Kubernetes era?

New Hope (2025–2026)23,788 chars · 53 min read