Skip to content

Repository files navigation

crypto-lab-noise-pipe

Noise Protocol · X25519 · AES-256-GCM · HKDF-SHA-256

What It Is

The Noise Protocol Framework is a framework for constructing cryptographic handshake protocols from composable patterns. Instead of negotiating cipher suites like TLS, you choose a handshake pattern — a fixed sequence of X25519 (Curve25519 ECDH) Diffie-Hellman operations — and the security properties follow deterministically from the pattern definition. Key material is derived throughout the handshake using HKDF-SHA-256 applied to a chaining key and running handshake hash; once the handshake completes, both parties hold symmetric AES-256-GCM transport keys with independent send and receive nonce counters. The security model is asymmetric during the handshake (static and ephemeral Diffie-Hellman key pairs) and symmetric during transport (256-bit-keyed AEAD encryption). The optional PSK mode used in IKpsk2 adds a pre-shared symmetric secret as a post-quantum defensive hedge without changing the handshake round-trips.

When to Use It

  • You need a secure channel without TLS PKI — Noise eliminates certificate authorities and in-protocol cipher-suite negotiation, so there is no in-protocol downgrade path; the pattern chosen at design time determines all authentication and secrecy properties with no runtime negotiation. Two responsibilities stay with the application: any version or pattern choice it negotiates before the handshake must be bound into the prologue (or another authenticated context) or downgrade returns through that door, and the static public keys still need a trustworthy distribution mechanism — no CA does not mean no trust bootstrap.
  • You need precisely scoped authentication — patterns such as XX (mutual), NK (known responder static key), NN (anonymous), and IKpsk2 (mutual + PSK) let you express exactly the authentication model your application requires, not whatever TLS negotiates.
  • You are building a peer-to-peer or embedded transport — Noise is designed for application-layer use where PKI infrastructure is absent or impractical, such as VPN tunnels, IoT devices, or payment-channel networks.
  • You require proven forward secrecy — every Noise pattern derives its transport keys from per-session ephemeral X25519 key pairs, so recorded transport traffic stays secure even if long-term static keys are later compromised. Read that as scoped to transport, not to the whole session. Patterns that pre-share the responder's static key (NK, KK, XK, IK, IKpsk2) encrypt their first message payload before the ephemeral-ephemeral DH runs, using only es and ss. The Noise spec's payload-security table grades that payload destination 2 — "encryption to a known recipient, forward secrecy for sender compromise only" — so an attacker who records the handshake and later steals the responder's static key can decrypt it. In IK and IKpsk2 that first message also carries the initiator's static key, so its identity falls the same way (spec §7.8 grade 4: encrypted, but without forward secrecy). This is the same trade-off as TLS 1.3 0-RTT early data, and it is what buys the round trip these patterns save. One further direction-specific caveat (spec §7.7): in patterns starting with K or I, the responder is guaranteed only weak forward secrecy for the transport messages it sends until it receives a transport message from the initiator. If you need forward secrecy for the first payload too, use XX or NX, which encrypt nothing before ee runs.
  • Do not use Noise when you need interoperability with existing TLS-based infrastructure (web servers, browsers, HTTPS APIs) — Noise is not TLS and does not speak the TLS record protocol.
  • Do NOT treat this as production code — it is a teaching demo for stepping through handshake patterns, not a hardened Noise implementation.

Live Demo

systemslibrarian.github.io/crypto-lab-noise-pipe

Select from thirteen handshake patterns (NN, NK, NX, KN, IN, KX, XN, XK, KK, IX, IK, XX, IKpsk2). The demo opens on NN — the simplest complete handshake (two ephemeral keys, one DH, no identities) — and offers a guided path (NN → NK → XX → IKpsk2) with a "what's new in this pattern vs the last" banner, so a newcomer meets one new idea at a time before the full thirteen-pattern grid. Patterns are ordered simplest → most complex throughout.

An "Anatomy of a token" onboarding step animates the DH-token notation directly: pick ee/es/se/ss and watch the two labeled key icons (e.g. my ephemeral × their static) slide into a DH mixer that emits the chaining-key update — and toggling Initiator ↔ Responder physically swaps which operand is yours, making the role-dependent es/se flip concrete instead of a tooltip.

Step through each handshake message one at a time, observing real X25519 DH scalar multiplications (each shown as which two public keys were multiplied, not just an output blob), chaining-key evolution via HKDF-SHA-256, and running handshake-hash updates. The per-party state is split into two captioned tracks — a transcript lane for h ("everything said so far, hashed — binds every handshake byte into one fingerprint") and a key-schedule lane for ck→k ("the secret that grows with each DH") — and each token is labeled binds to transcript and/or adds to key schedule as it fires. A segmented on-the-wire byte-block view shows each message's bytes (ephemeral pubkey · encrypted/plaintext static · AEAD tag), lighting up when the static key becomes encrypted (a k now exists) versus sent in the clear — making identity hiding visible per pattern. A predict-before-you-step prompt lets you commit to an answer and then reveals whether only h changes or h and ck/k both change.

After handshake completion, encrypt and decrypt plaintext messages using the derived AES-256-GCM transport keys with live nonce tracking; a Pattern Comparison panel shows NN, XX, IK, and IKpsk2 side-by-side, and a WireGuard Deep Dive panel maps IKpsk2 token-by-token to WireGuard's actual Initiator and Responder messages.

The Break it panel proves security properties by live attack rather than by badge — bit-flip tamper (AEAD tag), responder-static swap run as a genuine impersonation — the attacker holds the private key for the substituted rs and answers in the responder's place, so IK/NK/XK/KK complete the handshake with it and hand it the same transport key the initiator derived, while IKpsk2 refuses because the attacker never had the PSK — transport nonce reuse (leaking the plaintext XOR), PSK mismatch, message replay, and a forward-secrecy demonstration: it records a transport message, then "compromises" both static private keys after the fact and shows the recorded ciphertext still cannot be decrypted (the session key came from the discarded ephemeral DH), contrasted against a hypothetical static-only channel that the same attacker decrypts instantly.

What Can Go Wrong

  • Wrong pattern chosen for the threat model — selecting NN when authentication is required means either party can be impersonated by anyone; Noise provides no runtime negotiation or fallback to detect this mismatch.
  • IK or IKpsk2 with an unverified static key — these patterns assume the initiator already holds the responder's authentic static public key; if this key is substituted by an attacker (e.g., via a compromised key-distribution channel), the responder can be fully impersonated without breaking X25519.
  • AES-256-GCM nonce counter exhaustion — the transport nonce is a 64-bit counter, and reusing one under the same key would be catastrophic for AES-GCM's authentication and confidentiality. Noise's answer is to make exhaustion a hard stop rather than a wraparound: the spec reserves the maximum value 2⁶⁴−1, and requires that once incrementing n reaches it, any further encrypt or decrypt call signals an error to the caller (spec §5.1) — a spec-compliant session therefore refuses to continue rather than repeating a nonce. The failure mode to design for is an application that must rekey or reconnect after 2⁶⁴−1 messages, not one that silently loses confidentiality. (Demo caveat: this teaching implementation tracks the nonce as a JavaScript number and trips its "must rekey" guard at 2⁵³−1, the largest exactly-representable integer, rather than the spec ceiling of 2⁶⁴−1. The behaviour is faithful; the exact threshold is an approximation. A production build would use a BigInt or 64-bit counter.)
  • PSK reuse in IKpsk2 — the pre-shared key must be rotated out-of-band; a long-lived PSK that is never rotated steadily erodes its post-quantum and identity-hiding contributions, especially if the PSK is shared across multiple sessions.
  • Handshake hash not bound at the application layer — if the application does not verify the channel binding (the final handshake hash) out-of-band or via a higher-level protocol message, a network-level adversary can attempt session confusion attacks across concurrent connections.

Real-World Usage

  • WireGuard — uses the Noise IKpsk2 pattern as its entire VPN handshake; the pattern's mutual static-key authentication, ephemeral forward secrecy, and PSK layer map directly to WireGuard's Initiator and Responder handshake messages (WireGuard paper, Donenfeld 2017).
  • Lightning Network — BOLT #8 specifies Noise_XK_secp256k1_ChaChaPoly_SHA256 for encrypted transport between Lightning nodes, providing forward secrecy and responder identity hiding without a PKI.
  • WhatsApp — the transport layer between WhatsApp clients and servers uses a Noise-based protocol, providing forward secrecy and mutual authentication independently of the Signal end-to-end encryption layer.
  • libp2p — the peer-to-peer networking library used by IPFS and Ethereum clients implements Noise XX as its default secure channel protocol (libp2p Noise spec), providing mutual authentication and identity hiding for both peers.

How to Run Locally

git clone https://github.com/systemslibrarian/crypto-lab-noise-pipe
cd crypto-lab-noise-pipe
npm install
npm run dev

Correctness & Tests

The handshake is a real, spec-faithful implementation (CipherState / SymmetricState / HandshakeState, PSK, Split, Rekey) — not a mock. To keep it honest, npm test runs a Vitest suite that:

  • Cross-checks the full handshake against published Noise test vectors from the noise-c suite (also reproduced verbatim by Rust snow and Haskell cacophony). For NN, XX, KK, and IK it asserts every handshake message's exact wire bytes, the final handshake hash (channel binding), and the post-Split() transport ciphertexts — byte for byte.
  • Anchors the primitives with known-answer tests: X25519 (RFC 7748 §6.1), HKDF-SHA-256 (RFC 5869 Test Case 1), SHA-256 and AES-256-GCM (NIST) digests, and the AES-GCM nonce encoding (Noise §12.4, big-endian).
  • Verifies structural invariants and failure modes across every advertised pattern: both parties derive matching transport keys, AEAD rejects bit-flips and wrong keys, a substituted responder static key lets an impersonator complete the handshake and derive the initiator's transport key (IK/NK/XK — the pre-known rs is trusted unconditionally) while IKpsk2 stops the same attacker for want of the PSK, mismatched PSKs break the handshake, and replayed first messages are (correctly) accepted since core Noise has no replay protection.

These vectors caught a real bug during development: AES-GCM nonces were being encoded little-endian instead of big-endian, which silently corrupted every message after the first keyed one (n >= 1). The fix is anchored by the KATs above.

Related Demos


Part of the Crypto Lab suite.

"So whether you eat or drink or whatever you do, do it all for the glory of God." — 1 Corinthians 10:31

About

Browser-based Noise Protocol Framework demo — NN, XX, IK, and IKpsk2 handshake patterns with real X25519 arithmetic, live transport encryption, and a WireGuard deep dive. No backends. No simulated math.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages