Libp2p.Noise (libp2p_elixir v0.9.0)

Implements the Noise secure channel handshake for Libp2p.

This module handles the cryptographic handshake that upgrades a raw connection to an encrypted, authenticated session. It specifically implements the Noise_XX_25519_ChaChaPoly_SHA256 protocol.

Protocol Details

  • Pattern: XX (Mutual authentication, 3 messages).
  • DH Curve: 25519 (X25519).
  • Cipher: ChaChaPoly (ChaCha20-Poly1305).
  • Hash: SHA256.

Handshake Flow (XX Pattern)

The handshake consists of three messages exchanged between the Initiator (I) and Responder (R):

  1. I -> R: e (Initiator sends ephemeral public key).
  2. R -> I: e, ee, s, es (Responder sends ephemeral key, performs DH, sends encrypted static key and payload).
  3. I -> R: s, se (Initiator sends encrypted static key and payload).

After this exchange, both parties share two CipherState objects for encrypting traffic in each direction.

Authentication

Libp2p distinguishes between the "Noise Static Key" (used for the session) and the persistent "Identity Key" (e.g., the peer's Ed25519 key). To bind these together, the handshake payload includes:

  • The Identity Public Key.
  • A signature of the Noise Static Key, signed by the Identity Private Key.

This ensures that the peer controlling the Noise session also controls the claimed Libp2p Identity.

Summary

Functions

Initiator: produce first handshake message (message 1: -> e).

Initiator: consume msg2 and produce msg3 (message 3: -> s, se). Returns {msg3, st3, {cs_out, cs_in}} where cipher states are ready for transport.

Responder: consume msg3, verify payload, and return {st3, {cs_in, cs_out}}.

Responder: consume msg1 and produce msg2 (message 2: <- e, ee, s, es). Returns {msg2, st2}.

Decrypt a transport message with a CipherState.

Encrypt a transport message with a CipherState.

Types

cipher_state()

@type cipher_state() :: %{k: binary() | nil, n: non_neg_integer()}

role()

@type role() :: :initiator | :responder

state()

@type state() :: %{
  role: role(),
  ck: binary(),
  h: binary(),
  cs: cipher_state(),
  hkdf_swap?: boolean(),
  nonce_be?: boolean(),
  s_priv: binary(),
  s_pub: binary(),
  e_priv: binary() | nil,
  e_pub: binary() | nil,
  re: binary() | nil,
  rs: binary() | nil,
  identity: Libp2p.Identity.t() | nil,
  remote_identity_key: {atom(), binary()} | nil,
  local_stream_muxers: [binary()],
  remote_stream_muxers: [binary()],
  selected_stream_muxer: binary() | nil
}

Functions

deframe(bin)

@spec deframe(binary()) :: {binary(), binary()} | :more

frame(noise_message)

@spec frame(binary()) :: binary()

initiator_msg1(st)

@spec initiator_msg1(state()) :: {binary(), state()}

Initiator: produce first handshake message (message 1: -> e).

initiator_msg3(st, msg2)

@spec initiator_msg3(state(), binary()) ::
  {binary(), state(), {cipher_state(), cipher_state()}}

Initiator: consume msg2 and produce msg3 (message 3: -> s, se). Returns {msg3, st3, {cs_out, cs_in}} where cipher states are ready for transport.

new(role, identity)

@spec new(role(), Libp2p.Identity.t()) :: state()

new(role, identity, prologue)

@spec new(role(), Libp2p.Identity.t(), binary()) :: state()

new(role, identity, prologue, hash_protocol_name?)

@spec new(role(), Libp2p.Identity.t(), binary(), boolean()) :: state()

new(role, identity, prologue, hash_protocol_name?, hkdf_swap?)

@spec new(role(), Libp2p.Identity.t(), binary(), boolean(), boolean()) :: state()

new(role, identity, prologue, hash_protocol_name?, hkdf_swap?, nonce_be?)

@spec new(role(), Libp2p.Identity.t(), binary(), boolean(), boolean(), boolean()) ::
  state()

responder_finish(st, msg3)

@spec responder_finish(state(), binary()) ::
  {state(), {cipher_state(), cipher_state()}}

Responder: consume msg3, verify payload, and return {st3, {cs_in, cs_out}}.

responder_msg2(st, msg1)

@spec responder_msg2(state(), binary()) :: {binary(), state()}

Responder: consume msg1 and produce msg2 (message 2: <- e, ee, s, es). Returns {msg2, st2}.

transport_decrypt(cs, ciphertext_and_tag, ad \\ <<>>)

@spec transport_decrypt(cipher_state(), binary(), binary()) ::
  {binary(), cipher_state()}

Decrypt a transport message with a CipherState.

transport_encrypt(cs, plaintext, ad \\ <<>>)

@spec transport_encrypt(cipher_state(), binary(), binary()) ::
  {binary(), cipher_state()}

Encrypt a transport message with a CipherState.

Per Noise message format, the output is ciphertext || tag (16 bytes tag).