Skip to content

Commit

Permalink
add support for dynamic message handling
Browse files Browse the repository at this point in the history
  • Loading branch information
arik-so committed Nov 26, 2019
1 parent 037279b commit 84a0c9d
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 14 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ npm install bolt08
### Initiate Handshake

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

// we initialize a new node instance with a local private key
Expand Down Expand Up @@ -45,7 +45,7 @@ const serializedMessage = transmissionHandler.send(message); // serializedMessag
### Respond to Handshake

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

// we initialize a new node instance with a local private key
Expand Down
78 changes: 77 additions & 1 deletion src/handshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Chacha from './chacha';
const debug = debugModule('bolt08:handshake');
const secp256k1 = ecurve.getCurveByName('secp256k1');

enum Role {
export enum Role {
INITIATOR,
RECEIVER
}
Expand All @@ -21,6 +21,7 @@ enum Role {
export default class Handshake {

private role?: Role;
private nextActIndex: number = -1;

private privateKey: Bigi;
private publicKey: Point;
Expand Down Expand Up @@ -52,6 +53,78 @@ export default class Handshake {
this.hash = new HandshakeHash(Buffer.concat([this.chainingKey, prologue]));
}

public actDynamically({role, incomingBuffer, ephemeralPrivateKey, remotePublicKey}: { role?: Role, incomingBuffer?: Buffer, ephemeralPrivateKey?: Buffer, remotePublicKey?: Buffer }): { responseBuffer?: Buffer, transmissionHandler?: TransmissionHandler } {
if (!(this.role in Role)) {
if (!(role in Role)) {
throw new Error('invalid role');
}

this.assumeRole(role);
this.nextActIndex = 0;
}

let responseBuffer: Buffer = null;
let txHander: TransmissionHandler;

// we generate a local, static ephemeral private key
if (!this.ephemeralPrivateKey) {
if (!ephemeralPrivateKey) {
ephemeralPrivateKey = crypto.randomBytes(32);
}
this.ephemeralPrivateKey = Bigi.fromBuffer(ephemeralPrivateKey);
}

if (this.nextActIndex === 0) {
if (this.role === Role.INITIATOR) {
// we are starting the communication

if (!remotePublicKey) {
throw new Error('remote public key must be known to initiate handshake');
}

responseBuffer = this.serializeActOne({
ephemeralPrivateKey: this.ephemeralPrivateKey.toBuffer(32),
remotePublicKey: remotePublicKey
});

this.nextActIndex = 1; // next step: process incoming act two
} else {
if (!incomingBuffer) {
throw new Error('incoming message must be known to receive handshake');
}
this.processActOne(incomingBuffer);

responseBuffer = this.serializeActTwo({ephemeralPrivateKey: this.ephemeralPrivateKey.toBuffer(32)});

this.nextActIndex = 2; // next step: process incoming act three
}

} else if (this.nextActIndex === 1 && this.role === Role.INITIATOR) {
if (!incomingBuffer) {
throw new Error('incoming message must be known to receive handshake');
}
this.processActTwo(incomingBuffer);

responseBuffer = this.serializeActThree();
txHander = this.transmissionHandler;
this.nextActIndex = -1; // we are done
} else if (this.nextActIndex === 2 && this.role === Role.RECEIVER) {
if (!incomingBuffer) {
throw new Error('incoming message must be known to receive handshake');
}
this.processActThree(incomingBuffer);
txHander = this.transmissionHandler;
this.nextActIndex = -1; // we are done
} else {
throw new Error('invalid state!');
}

return {
responseBuffer,
transmissionHandler: txHander
};
}

public get transmissionHandler(): TransmissionHandler {
if (!this.txHandler) {
throw new Error('act 3 must be completed before a transmission handler is available');
Expand All @@ -61,6 +134,9 @@ export default class Handshake {

private assumeRole(role: Role) {
if (this.role in Role) {
if (role === this.role) {
return; // nothing is changing
}
throw new Error('roles cannot change!');
}
this.role = role;
Expand Down
113 changes: 102 additions & 11 deletions test/handshake.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as crypto from 'crypto';
import Handshake, {Role} from '../src/handshake';
import TransmissionHandler from '../src/transmission_handler';
import Bigi = require('bigi');
import chai = require('chai');
import ecurve = require('ecurve');
import Handshake from '../src/handshake';
import TransmissionHandler from '../src/transmission_handler';

const assert = chai.assert;
const secp256k1 = ecurve.getCurveByName('secp256k1');

describe('Handshake Tests', () => {

it('should simulate the handshake', async () => {
it('should simulate the handshake', () => {
// the counterparty's pubkey is known a priori
const localPrivateKey = Buffer.from('1111111111111111111111111111111111111111111111111111111111111111', 'hex');
const localPublicKey = secp256k1.G.multiply(Bigi.fromBuffer(localPrivateKey));
Expand All @@ -36,7 +36,7 @@ describe('Handshake Tests', () => {
{
const ephemeralPrivateKey = Buffer.from('1212121212121212121212121212121212121212121212121212121212121212', 'hex');

actOneMessage = await senderHandshake.serializeActOne({
actOneMessage = senderHandshake.serializeActOne({
ephemeralPrivateKey: ephemeralPrivateKey,
remotePublicKey: remotePublicKey.getEncoded(true)
});
Expand All @@ -47,7 +47,7 @@ describe('Handshake Tests', () => {
{
// the roles are flipped

await receiverHandshake.processActOne(actOneMessage);
receiverHandshake.processActOne(actOneMessage);
assert.equal(receiverHandshake['hash'].value.toString('hex'), '9d1ffbb639e7e20021d9259491dc7b160aab270fb1339ef135053f6f2cebe9ce');
}
}
Expand All @@ -59,13 +59,13 @@ describe('Handshake Tests', () => {
// SEND
{
const ephemeralPrivateKey = Buffer.from('2222222222222222222222222222222222222222222222222222222222222222', 'hex');
actTwoMessage = await receiverHandshake.serializeActTwo({ephemeralPrivateKey});
actTwoMessage = receiverHandshake.serializeActTwo({ephemeralPrivateKey});
assert.equal(actTwoMessage.toString('hex'), '0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae');
}

// RECEIVE
{
await senderHandshake.processActTwo(actTwoMessage);
senderHandshake.processActTwo(actTwoMessage);
assert.equal(senderHandshake['hash'].value.toString('hex'), '90578e247e98674e661013da3c5c1ca6a8c8f48c90b485c0dfa1494e23d56d72');
}
}
Expand All @@ -78,24 +78,115 @@ describe('Handshake Tests', () => {

// SEND
{
actThreeMessage = await senderHandshake.serializeActThree();
actThreeMessage = senderHandshake.serializeActThree();
assert.equal(actThreeMessage.toString('hex'), '00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba');
senderTxHandler = senderHandshake.transmissionHandler;
}

// RECEIVE
{
await receiverHandshake.processActThree(actThreeMessage);
receiverHandshake.processActThree(actThreeMessage);
receiverTxHandler = receiverHandshake.transmissionHandler;
}

assert.equal(senderTxHandler['sendingKey'].toString('hex'), '969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9');
assert.equal(senderTxHandler['receivingKey'].toString('hex'), 'bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442');

assert.equal(senderTxHandler['sendingKey'].toString('hex'), receiverTxHandler['receivingKey'].toString('hex'));
assert.equal(receiverTxHandler['sendingKey'].toString('hex'), senderTxHandler['receivingKey'].toString('hex'));

// conduct bilateral encoding test
const randomMessage = crypto.randomBytes(100);
const encryptedMessage = receiverTxHandler.send(randomMessage);
const receivedMessage = senderTxHandler.receive(encryptedMessage);
assert.equal(randomMessage.toString('hex'), receivedMessage.toString('hex'));
}
});

it('should simulate the handshake dynamically', () => {
// the counterparty's pubkey is known a priori
const localPrivateKey = Buffer.from('1111111111111111111111111111111111111111111111111111111111111111', 'hex');
const localPublicKey = secp256k1.G.multiply(Bigi.fromBuffer(localPrivateKey));

const remotePrivateKey = Buffer.from('2121212121212121212121212121212121212121212121212121212121212121', 'hex');
const remotePublicKey = secp256k1.G.multiply(Bigi.fromBuffer(remotePrivateKey));

// ACT 1:
// chaining_key: accumulated hash of previous ECDH outputs (whatever exactly that means)
// handshake_hash: accumulated hash of all handshake data, sent and received
// temporary_keys[0-2]: intermediate keys for AEAD payloads
// ephemeral_key: new keypair per session
// static_key: presumably the node's public keypair

const senderHandshake = new Handshake({privateKey: localPrivateKey});
const receiverHandshake = new Handshake({privateKey: remotePrivateKey});

let senderOutput: any;
let receiverOutput: any;

// ACT 1:
{

// SEND
{
const ephemeralPrivateKey = Buffer.from('1212121212121212121212121212121212121212121212121212121212121212', 'hex');

senderOutput = senderHandshake.actDynamically({
role: Role.INITIATOR,
ephemeralPrivateKey,
remotePublicKey: remotePublicKey.getEncoded(true)
});
assert.equal(senderOutput.responseBuffer.toString('hex'), '00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a');
}

// RECEIVE
{
// the roles are flipped
const ephemeralPrivateKey = Buffer.from('2222222222222222222222222222222222222222222222222222222222222222', 'hex');
receiverOutput = receiverHandshake.actDynamically({
role: Role.RECEIVER,
ephemeralPrivateKey,
incomingBuffer: senderOutput.responseBuffer
});
assert.equal(receiverOutput.responseBuffer.toString('hex'), '0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae');
}
}

// ACT 2:
{

// RECEIVE
{
senderOutput = senderHandshake.actDynamically({
incomingBuffer: receiverOutput.responseBuffer
});
assert.equal(senderOutput.responseBuffer.toString('hex'), '00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba');
}
}

// Act 3:
{

// RECEIVE
{
receiverOutput = receiverHandshake.actDynamically({
incomingBuffer: senderOutput.responseBuffer
});
}

const senderTxHandler: TransmissionHandler = senderOutput.transmissionHandler;
const receiverTxHandler: TransmissionHandler = receiverOutput.transmissionHandler;

assert.equal(senderTxHandler['sendingKey'].toString('hex'), '969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9');
assert.equal(senderTxHandler['receivingKey'].toString('hex'), 'bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442');

assert.equal(senderTxHandler['sendingKey'].toString('hex'), receiverTxHandler['receivingKey'].toString('hex'));
assert.equal(receiverTxHandler['sendingKey'].toString('hex'), senderTxHandler['receivingKey'].toString('hex'));

// conduct bilateral encoding test
const randomMessage = crypto.randomBytes(100);
const encryptedMessage = await receiverTxHandler.send(randomMessage);
const receivedMessage = await senderTxHandler.receive(encryptedMessage);
const encryptedMessage = receiverTxHandler.send(randomMessage);
const receivedMessage = senderTxHandler.receive(encryptedMessage);
assert.equal(randomMessage.toString('hex'), receivedMessage.toString('hex'));
}
});
Expand Down

0 comments on commit 84a0c9d

Please sign in to comment.