The Noise protocol framework
Noise from first principles: handshake patterns, the state-machine triple (Cipher/Symmetric/Handshake), why WireGuard chose Noise IK, and how to read pattern notation.
Before Noise, every new secure-transport protocol invented its own slightly-different handshake. The IPsec IKE family, SSH-Transport, TLS 1.2, ZRTP, WhatsApp's pre-Signal, dozens of obscure VoIP and IoT protocols — each had a custom authenticated key exchange built from the same primitives in slightly different ways. Many had subtle bugs. The cryptographers who designed each one were doing similar work in different vocabularies, with similar pitfalls.
Trevor Perrin's Noise Protocol Framework (2015) is the answer. It's a framework, not a protocol: a small alphabet of handshake tokens, a few cryptographic primitives slotted in, and a precise notation for composing them into a specific handshake pattern. Pick a pattern that matches your security and round-trip needs; the framework gives you the construction with formal-verification-friendly properties. WireGuard, Lightning Network, WhatsApp's noise transport, the I2P transport layer, and many others have adopted Noise as their handshake building block. This module is the foundational pass: enough to read a Noise pattern specification, recognize the three state objects, and understand why Noise IK is what WireGuard ended up using.
Prerequisites
- Module 2.2 — Stream ciphers and AEAD construction. Noise uses AEAD as its building block for confidentiality + integrity.
- Module 2.4 — Asymmetric crypto. Noise's DH operations are the elliptic-curve key agreement we covered there.
- Module 2.6 — Key derivation: HKDF and friends. Noise's chaining-key evolution is HKDF underneath.
Learning objectives
By the end of this module you should be able to:
- Explain what Noise is — a framework for composing handshake patterns from a small set of tokens — and what it isn't (a complete transport protocol on its own).
- Read a Noise handshake pattern such as
IKorXXand predict which messages carry which authentication material. - Describe how the three state objects (
CipherState,SymmetricState,HandshakeState) evolve as a handshake progresses. - Explain why WireGuard chose Noise IK and what tradeoffs that pattern made.
- Recognize when Noise is the right answer for a custom protocol design and when it isn't.
Why a framework instead of bespoke handshakes
Look at the engineering history of secure transports through ~2014:
- TLS 1.2 had a multi-page handshake state machine, with cipher-suite-dependent variations and a long history of bugs at the boundaries.
- IPsec IKEv1 had a complex two-phase exchange with optional Aggressive Mode, fragmentation, and identity-protection variations that interacted in ways nobody fully diagrammed.
- SSH-Transport had a working handshake but with its own custom KEX algorithm negotiation.
- WhatsApp's pre-Signal protocol invented its own handshake on top of Curve25519.
- IM and VoIP protocols routinely shipped their own handshake designs, sometimes with subtle authentication-binding bugs.
Each of these designers was making roughly the same engineering decisions. The cipher choice. The DH curve choice. Which messages are encrypted vs cleartext. Which keys come from which DH operation. How the transcript binds. How the cipher-state evolves into application-data keys. These are deep design decisions, with many subtle ways to get them wrong.
Trevor Perrin's insight: most of these handshakes are pattern-equivalent. They differ in:
- Which keys are pre-known to which party.
- The order in which ephemeral and static keys are exchanged.
- Which messages carry which authentication material.
A framework that captured these dimensions explicitly would let designers pick a pattern (with its known security properties) instead of inventing one. The pattern's properties — when you have authentication, when you have confidentiality, when you have identity hiding — would be derivable from the pattern itself, not from re-running formal proofs per protocol.
That's Noise. The framework defines:
- A small alphabet of tokens (
e,s,ee,es,se,ss,psk). - A small set of primitives (DH, hash, AEAD).
- A precise rule for composing tokens into messages, which messages flow in which direction, and what each token does to the state.
You pick a pattern by name (IK, XX, NK, KN, ...). You pick primitives (Curve25519 + ChaCha20-Poly1305 + SHA-256, say). The combination is a complete handshake.
The four primitive choices
A Noise protocol is a tuple: (pattern, DH function, cipher function, hash function).
The DH function is the curve and operation used for key agreement. Default and most common: Curve25519 with X25519. Alternatives: Curve448, NIST P-256.
The cipher function is the AEAD. Default and most common: ChaCha20-Poly1305. Alternatives: AES-GCM-128, AES-GCM-256.
The hash function is the underlying hash and HMAC for HKDF-style key derivation. Default and most common: SHA-256. Alternatives: SHA-512, BLAKE2s, BLAKE2b.
The pattern is the abstract handshake, e.g., IK or XX. We'll cover patterns next.
The complete protocol name is Noise_<pattern>_<DH>_<cipher>_<hash> — for example, Noise_IK_25519_ChaChaPoly_SHA256 is what WireGuard uses internally.
This combinatorial structure means a designer choosing Noise picks the pattern that matches their needs and gets the cipher/hash tradeoffs separately. The pattern is the design decision; the primitive choice is the engineering decision.
Tokens
A Noise message is a sequence of tokens. Each token represents either a key contribution or a DH operation. The tokens:
e— send an ephemeral public key. The sender generates a fresh ephemeral keypair and includes the public key in this message.s— send a static public key. The sender's long-term identity, possibly encrypted under whatever the current cipher state allows.ee— perform an ephemeral-ephemeral DH and mix the result into the chaining key. (Sender's ephemeral × receiver's ephemeral.)es— perform an ephemeral-static DH and mix the result. (Sender's ephemeral × receiver's static.)se— perform a static-ephemeral DH and mix the result. (Sender's static × receiver's ephemeral.)ss— perform a static-static DH and mix the result. (Sender's static × receiver's static.)psk— mix a pre-shared key into the chaining key. (Used in the PSK extension.)
Pre-message tokens (declared before the handshake messages) describe what each side already knows: the responder's static, the initiator's static, etc. Pre-message knowledge changes the security properties of the handshake; pre-message-known static keys allow authentication to happen earlier.
Order matters. e before ee means the ephemeral key is sent and then immediately combined with the receiver's ephemeral. s after es means the static key is encrypted under the key derived from the previous DH operation.
Reading a Noise pattern is reading what each direction's message contains, in order.
The three state objects
Noise's specification is built around three state objects that the implementation maintains:
CipherState. A symmetric key plus a 64-bit nonce counter. Each call to EncryptWithAd (encrypt, with associated data) increments the nonce. After the handshake completes, two CipherStates are produced — one for each direction of traffic. Application data goes through these.
SymmetricState. Wraps a CipherState plus two extra fields: a chaining key (ck) and a hash (h). The chaining key accumulates information about the DH operations performed; the hash accumulates information about the entire transcript (every byte sent). When a DH operation happens during the handshake, the result is HKDF-derived into a new ck and a new symmetric key for the CipherState. The transcript hash binds everything: if any byte of the handshake differed, the hash differs, and downstream MACs fail.
HandshakeState. Wraps a SymmetricState plus the local static key, local ephemeral key, remote static key (if pre-known), and remote ephemeral key. Tracks whose turn it is to send. Per pattern, has a fixed sequence of message-sending and message-receiving operations.
The handshake proceeds by walking the pattern: each message processes its tokens (sends keys, performs DH operations, derives new chaining keys), encrypts the payload (if a key is available at this point), and updates the transcript hash.
When the pattern's last message is processed, both sides have the same transcript hash and chaining key. They Split() the SymmetricState into two CipherStates — one for sending, one for receiving — keyed off HKDF-Expand of the chaining key. From that point on, application data is encrypted with these CipherStates.
The elegance is that all the protocol design decisions are abstracted into the pattern. Implementations don't have to figure out "when to encrypt the static," "when to perform which DH," "what to hash for the transcript." The pattern says, the framework handles, the implementation just walks the spec.
Reading patterns: IK and XX
The two patterns most worth understanding:
XX — Mutually-authenticated, no pre-known keys. Three messages.
XX:
-> e
<- e, ee, s, es
-> s, se
Reading: initiator sends an ephemeral. Responder sends an ephemeral, performs ephemeral-ephemeral DH, sends its static (encrypted under the new key derived from ee), performs ephemeral-static DH. Initiator sends its static (encrypted), performs static-ephemeral DH.
After three messages, both sides have authenticated each other (each knows the other's static via the encrypted s tokens), and they share derived keys. Three round trips. Used in scenarios where neither side knows the other's static key in advance.
IK — Initiator knows responder's static in advance. Two messages.
IK:
<- s (pre-message: initiator already has responder's s)
-> e, es, s, ss
<- e, ee, se
Reading: pre-message — the initiator already knows the responder's static public key. Then: initiator sends ephemeral, performs ephemeral-static DH (using the pre-known responder static), sends its static (encrypted under the now-derived key), performs static-static DH. Responder sends ephemeral, performs ephemeral-ephemeral and static-ephemeral DH.
After two messages, mutual authentication is complete. One round trip — the initiator's first message contains its identity, and the responder's first reply contains everything else.
The tradeoff:
- XX doesn't require the initiator to know the responder's static in advance. Better identity-hiding properties early on. Three round trips.
- IK requires pre-known static. Faster (one round trip). The initiator's static identity is sent encrypted to a pre-known responder, so the initiator's identity isn't visible to passive observers.
For protocols where one side (the server) has a stable, well-known public key — TLS, WireGuard endpoints, message-routing services — IK is attractive. For peer-to-peer with no prior knowledge, XX is the right answer.
Why WireGuard chose Noise IK
WireGuard's protocol design is the canonical Noise IK deployment. The choices:
- IK pattern. Each WireGuard peer knows the other's static public key in advance (configured in the wg config files). The 1-RTT handshake (initiator → responder, responder → initiator) maps naturally onto IK.
- Curve25519 + ChaCha20-Poly1305 + BLAKE2s. Three modern primitives, all chosen for software performance and side-channel-friendliness.
- Pre-shared key extension. Noise IK with PSK provides post-quantum resistance hedge against future quantum compromise of Curve25519.
WireGuard's whitepaper describes the protocol as Noise IK with specific application-layer additions (cookie mechanism for DoS resistance, MAC1/MAC2 outer-packet authentication, etc.). The handshake itself is recognizable to anyone who's read Noise IK.
The benefit of using Noise: WireGuard's security properties were derivable from Noise IK's. Trevor Perrin's framework had been formally analyzed; WireGuard inherited the security argument by being a Noise IK instance with specific primitives. The protocol authors didn't have to re-prove what Noise IK had already established. This is the engineering payoff of using a framework instead of inventing.
What Noise leaves to the implementer
Noise specifies the handshake. It does not specify:
- How the application sends data after the handshake. Both sides have CipherStates; how to frame and order encrypted records is the implementer's choice. WireGuard has packet-by-packet encryption; another protocol might have stream-oriented framing.
- Replay protection beyond per-message nonces. Noise's CipherStates have monotonic nonces; if the network reorders or replays packets, the application has to handle it. WireGuard uses sliding-window replay rejection.
- DoS resistance. A naive Noise implementation will accept and process any handshake initiation. WireGuard's MAC1/MAC2 cookie mechanism prevents simple flooding.
- Identity validation logic. "I see this static public key on the other side; do I trust it?" Noise authenticates that the peer holds the corresponding private key, but the policy decision (is this key in my allowlist?) is application-level.
- Rekeying / key updates. Noise produces fresh keys per handshake. If you want long-lived connections to periodically refresh keys, you have to design that on top.
This is the "framework, not protocol" point. Noise gives you a robust, well-analyzed handshake. The transport semantics and policy logic remain your responsibility. Real protocols using Noise (WireGuard, Lightning) have substantial codebases beyond just the handshake — they're protocols built around a Noise core.
Hands-on exercise
Exercise 1 — Parse a handshake pattern with a tiny script
"""Walk a Noise handshake pattern and print what's sent/done at each step."""
# IK pattern: pre-message responder static, then two messages.
PRE_MESSAGES = [
("responder", ["s"]), # responder's static is pre-known to initiator
]
MESSAGES = [
("initiator", ["e", "es", "s", "ss"]),
("responder", ["e", "ee", "se"]),
]
def describe(token):
return {
"e": "send ephemeral public key",
"s": "send static public key (encrypted if key derived)",
"ee": "DH(ephemeral, ephemeral)",
"es": "DH(initiator's ephemeral, responder's static)",
"se": "DH(initiator's static, responder's ephemeral)",
"ss": "DH(initiator's static, responder's static)",
}.get(token, token)
print("=== Pre-messages ===")
for sender, tokens in PRE_MESSAGES:
print(f" ({sender} static is known to the other side)")
for i, (sender, tokens) in enumerate(MESSAGES, 1):
print(f"\n=== Message {i}: {sender} sends ===")
for t in tokens:
print(f" {t}: {describe(t)}")
Run it. The output is the IK handshake step by step. Notice that es — the first DH operation in the initiator's first message — happens before the initiator sends its static. That's why s can be encrypted in IK: by the time we write the static, we've already derived a key from the es DH.
In XX, the comparable encryption happens later (after ee in message 2). The pattern's specific token ordering determines which keys are encrypted under which derived secrets — the security properties follow directly from the order.
Stretch: add the XX pattern and walk it. Notice that XX takes three messages and does the static-key encryption on the second and third messages, where IK does it on the first.
Exercise 2 — Map Noise IK onto WireGuard
Open WireGuard's protocol page and look at the handshake initiation message. You should see fields named ephemeral, static, timestamp. Map these to the Noise IK pattern's first message: e, es, s, ss.
ephemeral=e(the initiator's ephemeral public key, in cleartext).static=s(the initiator's static public key, encrypted under the key derived aftereandes).timestamp= an additional encrypted payload; the static-staticssoperation happens before this is encrypted, so it's authenticated by all four DH operations.
Now look at the response: ephemeral, then a small encrypted blob. Maps to e, ee, se.
The protocol's "extra" parts (MAC1/MAC2 cookies, the empty payload structures) are application-level additions; the cryptographic core is recognizably Noise IK. This is the engineering payoff of using Noise — WireGuard's authors didn't reinvent the handshake; they instantiated a known-good pattern with their primitive choices.
Common misconceptions
"Noise is a VPN protocol." Noise is a framework for composing handshakes. WireGuard is a VPN protocol that uses Noise IK as its handshake. Calling Noise itself a VPN is like calling TLS-the-handshake a web browser.
"Pattern names like IK are arbitrary branding." Each letter encodes specific assumptions. The first letter is the initiator's static-key knowledge ("I" = initiator's static is sent in this handshake; "K" = pre-known to the responder; "X" = sent encrypted; "N" = no static for this side). The second letter is the responder's. Together they uniquely specify the handshake's authentication structure.
"Using Noise means the rest of the protocol is automatically safe." Noise specifies the handshake. Transport framing, replay handling, state management, DoS resistance, and identity-policy decisions are all implementer responsibilities. Many real-world bugs in Noise-based protocols have been at the layer above Noise.
"Identity hiding is always free." Different patterns trade identity-hiding for round trips and pre-known-key requirements. IK hides the initiator's static (encrypted in the first message) but requires the responder's static to be pre-known. XX hides static keys later in the exchange but takes more round trips. There's no pattern that "just hides everything always." Pick based on threat model.
"Noise replaces TLS everywhere." Noise solves a different design space. It excels at custom transports and point-to-point protocols where the designer controls both endpoints. TLS excels at the open public-internet case with X.509 PKI, ALPN negotiation, and the established middlebox ecosystem. They're complementary tools for overlapping but distinct problems.
Further reading
- Noise Protocol Framework specification. The authoritative spec. Surprisingly readable for a cryptographic protocol document — much shorter than TLS 1.3 or IKEv2.
- WireGuard whitepaper. Canonical Noise IK deployment. The cryptographic core fits in a few pages because it's "Noise IK with these primitives."
- The Lightning Network BOLT-08. Bitcoin Lightning's transport-encryption layer, also Noise-based.
- Trevor Perrin, The Noise Protocol Framework, RWC 2016 talk. The author's introduction. Worth watching once for the design rationale and what Noise was reacting to.
- Noise Explorer. Interactive tool for picking Noise patterns and seeing their security properties as they were formally analyzed in Bhargavan et al.'s work.
The next module — Post-quantum cryptography in transit — closes Track 2 by looking at what changes when quantum computers become powerful enough to break Curve25519 and RSA, and what protocol designers are doing about it today.
// related reading
Decoy routing and refraction networking
Telex, TapDance, Slitheen, and Conjure: how cooperative infrastructure on ordinary network paths changes the evasion game.
Hysteria and QUIC-based transports
Why QUIC became an evasive substrate, how Hysteria uses it, and what QUIC-based camouflage still leaks to modern detectors.
Operational anonymity for engineers
Compartmentation, browser discipline, transport choice, telemetry minimization, and how to turn anonymity theory into a survivable daily operating model.