0.2.7 • Published 4 years ago

bolt08 v0.2.7

Weekly downloads
4
License
MIT
Repository
github
Last release
4 years ago

bolt08

Build Status Coverage Status

A utility for interfacing with the TCP-based networking protocol outlined in Lightning Network's BOLT 8.

Install

npm install bolt08

Usage

In the examples below, each act is serialized and processed separately, with methods such as

handshakeHandler.serializeActOne({remotePublicKey});

or

handshakeHandler.processActTwo(incomingActTwo);

However, if you are comfortable with lending the tool some autonomy, you can simply repeatedly call

const output = handshakeHandler.actDynamically({
	role: Role.RECEIVER, // only necessary on first call
	incomingBuffer: inputData, // do not set for first message
	remotePublicKey: publicKey, // only necessary for handshake initiation
	ephemeralPrivateKey: ephemeralPrivateKey // optional
});

Where output will have the properties responseBuffer, unreadBuffer, and transmissionHandler.

If responseBuffer is non-empty, you still need to send a message to your peer.

If unreadBuffer is non-empty, you have only processed a portion of the incoming message, and should be aware of that when you deal with remaining binary buffers.

If transmissionHandler is set, you are now equipped to use it to send and receive regular, post-handshake messages. Key rotations will be taken care of automatically.

Example

Initiate Handshake

import {Handshake} from 'bolt08';
import * as crypto from 'crypto';

// we initialize a new node instance with a local private key
const privateKey = crypto.randomBytes(32);
const handshakeHandler = new Handshake({privateKey});

// we know the counterparty's public key, so we initiate the connection
const remotePublicKey = Buffer.from('028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7', 'hex');
const actOne = handshakeHandler.serializeActOne({remotePublicKey});

// we send act one to the remote party, and receive their act two response
// we do not go into detail for how the transmission is facilitated through TCP
const incomingActTwo: Buffer = oracle.sendAndReceive(actOne);
handshakeHandler.processActTwo(incomingActTwo);

// now we perform the final act
const actThree = handshakeHandler.serializeActThree();
oracle.send(actThree); // we do not care about the response

// having performed act three, we can now send messages back and forth
const transmissionHandler = handshakeHandler.transmissionHandler;

const message = Buffer.from('Hello World!', 'ascii');
const serializedMessage = transmissionHandler.send(message); // serializedMessage is what we send over TCP

Respond to Handshake

import {Handshake} from 'bolt08';
import * as crypto from 'crypto';

// we initialize a new node instance with a local private key
const privateKey = crypto.randomBytes(32);
const handshakeHandler = new Handshake({privateKey});

const incomingActOne: Buffer = oracle.receive();
handshakeHandler.processActOne(incomingActOne);

const actTwo = handshakeHandler.serializeActTwo({});
const incomingActThree = oracle.sendAndReceive(actTwo);

handshakeHandler.processActThree(incomingActThree);

// having processed act three, we can now send messages back and forth
const transmissionHandler = handshakeHandler.transmissionHandler;

const message = Buffer.from('Welcome!', 'ascii');
const serializedMessage = transmissionHandler.send(message); // serializedMessage is what we send over TCP

Interact with an LND Node

You can set up a local TCP server with node and test the connection with a real Lightning node.

Initiate Handshake to LND

First, we need to run LND

lnd

Assuming regtest and an unlocked wallet, let's also extract our identity pubkey

lncli -n regtest getinfo

In my case, it was 03c8f10269ebe6f67a703b7d934b10592f0a05f83c83571524a3771f5f921b8d3a, so let's initiate the handshake.

import * as net from 'net';
import {Handshake, Role, TransmissionHandler} from 'bolt08';
import * as crypto from 'crypto';

const localPrivateKey = crypto.randomBytes(32);
const remotePublicKey = Buffer.from('03c8f10269ebe6f67a703b7d934b10592f0a05f83c83571524a3771f5f921b8d3a', 'hex');

const client = new net.Socket();

const handshakeHandler = new Handshake({privateKey: localPrivateKey});
let transmissionHandler: TransmissionHandler;
let pendingData = Buffer.alloc(0);

function processIncomingData(data) {
	// there is some unprocessed data that we will prepend to the newly received data for processing
	const inputData = Buffer.concat([pendingData, data]);
	pendingData = Buffer.alloc(0);

	console.log('Processing:');
	console.log(inputData.toString('hex'));

	if (transmissionHandler instanceof TransmissionHandler) {
		const decryptedResponse = transmissionHandler.receive(inputData);
		console.log('Decrypted:');
		console.log(decryptedResponse.message.toString('hex'));
	} else {
		const output = handshakeHandler.actDynamically({incomingBuffer: inputData});
		if (output.responseBuffer && output.responseBuffer.length > 0) {
			const response = output.responseBuffer;

			console.log('Responding:');
			console.log(response.toString('hex'));
			client.write(response)
		}
		if (output.transmissionHandler && output.transmissionHandler instanceof TransmissionHandler) {
			transmissionHandler = output.transmissionHandler;
		}
		if (output.unreadBuffer && output.unreadBuffer.length > 0) {
			pendingData = output.unreadBuffer;
			// let's immediately process the remaining data in this case
			processIncomingData(Buffer.alloc(0));
		}
	}
}

client.connect(9735, '127.0.0.1', () => {
	console.log('connected');
	const firstActOutput = handshakeHandler.actDynamically({
		role: Role.INITIATOR,
		remotePublicKey
	});
	const firstMessage = firstActOutput.responseBuffer;
	console.log('First message:');
	console.log(firstMessage.toString('hex'));
	client.write(firstMessage);
});

client.on('data', (data) => {
	console.log('Received:');
	console.log(data.toString('hex'));
	processIncomingData(data);
});

client.on('error', (error) => {
	console.log('Error:');
	console.log(error);
});

client.on('close', function () {
	console.log('Connection closed');
});

The output might look something like this:

First message: 00039d4e60cd8dd41a2d008e06a114921af1932eb6ab7d50beeca666ed346dd960263b35bb394d0aa3245aa4bbf1a4b7fd0a

Received: 0002a39a406673bdb1797a5d1c1ad794aa77f694b5b1610ff5a409a02583aef2daf7d9c09e4dd3ff41589230d40525ef3bf9

Processing: 0002a39a406673bdb1797a5d1c1ad794aa77f694b5b1610ff5a409a02583aef2daf7d9c09e4dd3ff41589230d40525ef3bf9

Responding: 00d5fb4d6dfbfc8685b863db272d054d7caa04802902c7355a873951405c8142c0f73643d2966b83fb113ff9cc26f4ea800077b1184c0a0a0834f20fb79a781679ce

Received: bf242757aadaa93bf564873cc22031f224e5d2d2e21cdb7cb14b7430e458e75ad1aaf9ceee014cdcbc498c66

Processing: bf242757aadaa93bf564873cc22031f224e5d2d2e21cdb7cb14b7430e458e75ad1aaf9ceee014cdcbc498c66

Decrypted: 00100002220000022281

Process Handshake from LND

Alternatively, we can first start listening, and then initiate the handshake using lncli. To do so, we must know the node's public key beforehand. For this setup, we're gonna use a local public key 036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f7.

import * as net from 'net';
import {Socket} from 'net';
import {Handshake, Role, TransmissionHandler} from 'bolt08';

// publicKey: 036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f7
const privateKey = Buffer.from('1212121212121212121212121212121212121212121212121212121212121212', 'hex');
const port = 1337;

const handshakeHandler = new Handshake({privateKey});
let transmissionHandler: TransmissionHandler;
let pendingData = Buffer.alloc(0);

function processIncomingData(data, client: Socket) {
	// there is some unprocessed data that we will prepend to the newly received data for processing
	const inputData = Buffer.concat([pendingData, data]);
	pendingData = Buffer.alloc(0);

	console.log('Processing:');
	console.log(inputData.toString('hex'));

	if (transmissionHandler instanceof TransmissionHandler) {
		const decryptedResponse = transmissionHandler.receive(inputData);
		console.log('Decrypted:');
		console.log(decryptedResponse.message.toString('hex'));
	} else {
		const output = handshakeHandler.actDynamically({role: Role.RECEIVER, incomingBuffer: inputData});
		if (output.responseBuffer && output.responseBuffer.length > 0) {
			const response = output.responseBuffer;

			console.log('Responding:');
			console.log(response.toString('hex'));
			client.write(response)
		}
		if (output.transmissionHandler && output.transmissionHandler instanceof TransmissionHandler) {
			transmissionHandler = output.transmissionHandler;
		}
		if (output.unreadBuffer && output.unreadBuffer.length > 0) {
			pendingData = output.unreadBuffer;
			// let's immediately process the remaining data in this case
			processIncomingData(Buffer.alloc(0), client);
		}
	}
}

const server = net.createServer(function (client) {

	client.on('data', (data: Buffer) => {
		console.log('Received:');
		console.log(data.toString('hex'));
		processIncomingData(data, client);
	});

	client.on('error', (error) => {
		console.log('Error:');
		console.log(error);
	});

	client.on('close', function () {
		console.log('Connection closed');
	});

});

server.listen(port, '127.0.0.1');

Now we initiate the handshake using lncli.

lncli connect 036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f7@127.0.0.1:1337

The output from Node might then look something like this:

Received: 00026ef8318ce2124ef03deeb243e28b92befc76db39de95d2809470e9be7ab26fa41b1183c3133c3803c64a271690abb911

Processing: 00026ef8318ce2124ef03deeb243e28b92befc76db39de95d2809470e9be7ab26fa41b1183c3133c3803c64a271690abb911

Responding: 0003a30c775f2f6734d4a179a4ec78116f3a419f5baecc5a62632bf7d83ea080c7884f4c054fb88119f9812d5c2988f881b0

Received: 0039c112d508c1e26d5088e64467e8a57c087e5eae357aa7fa63f48d165b2ff5b8381a21fe79bed61d6454444ba36cf478b0ad22ff53c5bbb34cf42ef4c284549f7a2a83fc6cc9f85eda787ca725fa0c2b89a2dca947ce9ddc5cd629c4c2202252cfc6b96ba06a3f8b1cec7ec3db

Processing: 0039c112d508c1e26d5088e64467e8a57c087e5eae357aa7fa63f48d165b2ff5b8381a21fe79bed61d6454444ba36cf478b0ad22ff53c5bbb34cf42ef4c284549f7a2a83fc6cc9f85eda787ca725fa0c2b89a2dca947ce9ddc5cd629c4c2202252cfc6b96ba06a3f8b1cec7ec3db

Processing: 2a83fc6cc9f85eda787ca725fa0c2b89a2dca947ce9ddc5cd629c4c2202252cfc6b96ba06a3f8b1cec7ec3db

Decrypted received: 00100002220000022281

License

MIT

0.2.7

4 years ago

0.2.6

4 years ago

0.2.5

4 years ago

0.2.4

4 years ago

0.2.3

4 years ago

0.2.2

4 years ago

0.2.1

4 years ago

0.2.0

4 years ago

0.1.1

4 years ago

0.1.0

4 years ago