Table of Contents


NoiseSocket is an extension of the Noise Protocol Framework that enables quick and seamless secure connections with minimal code size, small keys, modern ciphers and hash functions, and extremely fast speed. It can be used with raw public keys instead of X.509 infrastructure and targets IoT devices, microservices, and back-end applications such as datacenter-to-datacenter communications.

1. Overview

The Noise Protocol Framework [1] describes simple Noise protocols. A Noise protocol sends a fixed sequence of handshake messages based on a fixed set of cryptographic choices. In some situations this is too rigid, and the responder needs flexibility to accept or reject the initiator's Noise protocol choice, or make its own choice based on options offered by the initiator.

The NoiseSocket framework allows the initiator and responder to negotiate a particular Noise protocol. This is a two-step process:

NoiseSocket doesn't specify the contents of negotiation data, since different applications will encode versions and advertise protocol support in different ways. NoiseSocket just defines a message format to transport this data, and APIs to access it.

NoiseSocket handles two other low-level issues:

2. Message Formats

A NoiseSocket protocol begins with a handshake phase. During the handshake phase each NoiseSocket message contains a single handshake message from some underlying Noise protocol, plus optional negotiation data.

After the handshake completes, NoiseSocket enters the transport phase where each NoiseSocket message contains a transport message from some underlying Noise protocol.

All transport messages and some handshake messages contain an encrypted Noise payload. Each encrypted payload contains a plaintext with a body (its actual contents) followed by padding.

The following sections describe the format for NoiseSocket handshake and transport messages, and encrypted payloads.

2.1. Handshake messages

All NoiseSocket handshake messages have the same structure:

The negotiation_data_len and noise_message_len fields are 2-byte unsigned integers, encoded in big-endian, that store the number of bytes for the following negotiation_data and noise_message fields.

2.2. Transport messages

All NoiseSocket transport messages have the same structure:

The noise_message_len field is a 2-byte unsigned integer, encoded in big-endian, that stores the number of bytes for the following noise_message field.

2.3. Encrypted payloads

Some Noise messages will carry an encrypted payload. When this payload is decrypted, the plaintext will have the following structure:

The body_len field is a 2-byte unsigned integer, encoded in big-endian, that stores the number of bytes for the following body field. Following the body field the remainder of the plaintext will be padding bytes, which may contain arbitrary data and must be ignored by the recipient.

3. Negotiation

The initiator will choose the initial underlying Noise protocol, and will indicate this to the responder using the negotiation_data field.

Upon receiving an initial NoiseSocket message, the responder has five options:

The initiator's first negotiation_data field must indicate the initial Noise protocol and what other Noise protocols the initiator can support. How this is encoded is up to the application.

If the responder's first negotiation_data field is empty, then the initial protocol was accepted. If the field is non-empty, it must encode values that distinguish betwen the "explicit rejection", "fallback", and "reinitialization request" cases. In the first case, the negotiation_data must encode an error message. In the latter two cases, the negotiation_data must encode the Noise protocol the initiator should fallback to or reinitialize with.

When the initiator receives the first NoiseSocket response message, and for all later handshake messages received by both parties, the only options are silent rejection, explicit rejection, or acceptance.

Example negotiation flows:

4. Prologue

Noise protocols take a prologue input. The prologue is cryptographically authenticated to make sure both parties have the same view of it.

The prologue for the initial Noise protocol is set to the UTF-8 string "NoiseSocketInit1" followed by all bytes transmitted prior to the noise_message_len. This consists of the following values concatenated together:

If the responder changes the Noise protocol, the prologue is set to the UTF-8 string "NoiseSocketInit2" followed by all bytes received and transmitted prior to the noise_message_len. This consists of the following values concatenated together:

5. API

The initiator uses the following functions during the handshake phase. These functions are described in the order they would typically be used to send the initial handshake message and process the first response. In particular, the initiator would "peek" at the negotiation data in the first response message, then decide whether reinitialization is necessary (if the negotiation data indicates a reinitialization request or a fallback message).






The server will use the same functions, except it will first "peek" at the initial message, then call Initialize if it is accepting the initial protocol, or Reinitialize if it is changing protocols with a fallback message or reinitialization request.

If the responder is sending an explicit rejection or reinitialization request, it will use the following function:


Following the first exchange of handshake messages, the parties will continue calling ReadHandshakeMessage and WriteHandshakeMessage until the handshake is complete.

After the handshake is complete, both parties will call WriteMessage and ReadMessage to send transport messages. Every call to WriteMessage will produce a NoiseSocket transport message, and every call to ReadMessage will decrypt a NoiseSocket transport message and return its body.



6. IPR

The NoiseSocket specification (this document) is hereby placed in the public domain.

7. Acknowledgements

Thanks to Rhys Weatherley for helpful discussion.

8. References

[1] T. Perrin, “The Noise Protocol Framework.” 2017.