Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
159 lines (113 sloc) 5.97 KB

Lightning Network Noise Protocol Socket (BOLT #8)

CircleCI codecov

Implements a Noise Protocol TCP Socket and Server in Node.js as defined in Lightning Network BOLT #8 for Encrypted and Authenticated Transport.

NoiseSocket enables confidentiality between two nodes by encrypting all traffic between them. All traffic is also authenticated against the node's known long-term identifier (Bitcoin public key as curve secp256k1).

NoiseSocket performs a cryptographic handshake upon connection and performs frequent key rotation. Once the handshake is complete, the ready event signals that messages can be sent or received.

NoiseSocket is a decorator for net.Socket. It maintains the same interface used by net.Socket and implements a Duplex stream. This enables reading from the stream in paused (readable event) or flowing mode (piped or data event).

Learn more about the Noise Protocol and the Lighting Network version:

Usage

Creating a NoiseSocket Client

This example shows how to create a NoiseSocket that connects to a NoiseServer.

To create a NoiseSocket, you must provide:

  • local private key ls which is a Buffer with 32-bytes
  • remote node's compressed public key rp which is a Buffer with 33-bytes.

These two values are required to create the encrypted and authenticated communication channel.

const noise = require('@lntools/noise');

// ls is private key as a Buffer(32) defining a point on elliptic
// curve secp256k1
const ls = Buffer.from('1111111111111111111111111111111111111111111111111111111111111111', 'hex');

// rpk is compressed public key as a Buffer(33) defining a point
// on elliptic curve secp256k1
const rpk = Buffer.from(
  '028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7',
  'hex'
);

// Create a new `NoiseSocket` instance using the `connect` factory
const socket = noise.connect({ ls, rpk, host: '127.0.0.1', port: 9735 });

// Socket will emit the `connect` event when the TCP connection is
// established, however the socket is not yet ready because the
// handshake has not be completed
socket.on('connect', () => {
  console.log('connected to server');
});

// Socket will emit the `ready` event once the handshake is completed.
// The socket is now ready for sending and receiving messages!
socket.on('ready', () => {
  console.log('handshake complete, ready for sending/receiving');

  // Send a message
  socket.write(Buffer.from('hello from client'));
});

// Attaching a `data` event will put the socket into flowing mode and will
// process messages as soon as they arrive.
socket.on('data', buf => {
  console.log('received: ' + buf.toString());
});

Creating a NoiseSocket Server:

const noise = require('@lntools/noise');

// ls is private key as a Buffer(32) defining a point on elliptic
// curve secp256k1
const ls = Buffer.from('2121212121212121212121212121212121212121212121212121212121212121', 'hex');

// Create a server bound to our local secret. New connections will be
// handled by the onSocket function.
const server = noise.createServer({ ls }, onSocket);

// Start the server listening on port 9735.
server.listen({ host: '127.0.0.1', port: 9735 });

// Handle when a remote socket connects to the server. The socket
// will be connected, but is not ready for use until the `ready`
// event is emitted. `socket` is an instance of `NoiseSocket`.
function onSocket(socket) {
  console.log('remote socket connected');

  // Socket will emit the `ready` event once the handshake is completed.
  // The socket is now ready for sending and receiving messages!
  socket.on('ready', () => {
    console.log('handshake complete, ready for sending/receiving');

    // Send a message
    socket.write(Buffer.from('hello from server'));
  });

  // Attaching a `data` event will put the socket into flowing mode and
  // will process messages as soon as they arrive.
  socket.on('data', buf => {
    console.log('received: ' + buf.toString());
  });
});

Reading Modes

Because Noisesocket is a Duplex stream and fully implements Readable functionality, it allows reading from the stream via both reading modes: flowing and paused.

Flowing mode is the recommended technique for consuming data and can be achieved by using the data event or piping the NoiseSocket to another stream.

The data event method reads data as soon as it comes in:

socket.on('data', buf => {
  /* do stuff with the buffer */
});

You can pipe responses to other streams:

let fs = require('fs');
let wr = fs.createWriteStream('capture.bin');

// pipe the raw buffer data to some other stream
socket.pipe(wr);

Lastly, you can use paused mode to manually read from the stream. readable events will be emitted when new data is available. This mechanism allows you to directly control how reads occur and is useful for implementing wire protocols ontop of the NoiseSocket.

socket.on('readable', () => {
  let buf = socket.read();
  if (buf) {
    /* do stuff with the buffer */
  }
});

More information about reading modes is available in the Node.js streams documentation.

Writing

Writing to the socket can be accomplished by calling write and passing in a Buffer.

let d = Buffer.from('some data');
socket.write(d);

The write method will return a boolean value indicating if you can continue to write to the socket. If write returns false, you must wait until the drain event is emitted before continuing to write data. If you do not respond to to the boolean flag, data will be buffered into memory.

You can’t perform that action at this time.