bip151: fixes and tests.

This commit is contained in:
Christopher Jeffrey 2016-07-21 09:21:36 -07:00
parent 8bcfeca44e
commit e7347dd620
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 271 additions and 9 deletions

View File

@ -37,6 +37,17 @@ function BIP151(cipher, key) {
this.prk = null;
this.tag = null;
this.seq = 0;
this.initReceived = false;
this.ackReceived = false;
this.initSent = false;
this.ackSent = false;
this.highWaterMark = 1024 * (1 << 20);
this.processed = 0;
this.lastRekey = 0;
this.timeout = null;
this.callback = null;
this.completed = false;
this.handshake = false;
this.pendingHeader = [];
this.pendingHeaderTotal = 0;
@ -69,6 +80,13 @@ BIP151.prototype.init = function init(publicKey) {
this.aead.aad(this.sid);
};
BIP151.prototype.isReady = function isReady() {
return this.initSent
&& this.ackReceived
&& this.initReceived
&& this.ackSent;
};
BIP151.prototype.rekey = function rekey() {
assert(this.prk, 'Cannot rekey before initialization.');
this.k1 = utils.hash256(this.k1);
@ -135,19 +153,35 @@ BIP151.prototype.toEncinit = function toEncinit(writer) {
if (!writer)
p = p.render();
this.initSent = true;
return p;
};
BIP151.prototype.fromEncinit = function fromEncinit(data) {
BIP151.prototype.encinit = function encinit(data) {
var p = bcoin.reader(data);
var publicKey = p.readBytes(33);
this.cipher = p.readU8();
this.init(publicKey);
// this.cipher = p.readU8();
assert(p.readU8() === this.cipher, 'Wrong cipher type.');
assert(!this.initReceived, 'Already initialized.');
if (!this.ackReceived) {
this.init(publicKey);
} else {
assert(utils.equal(publicKey, this.publicKey),
'Bad pubkey.');
}
this.lastRekey = utils.ms();
this.initReceived = true;
return this;
};
BIP151.fromEncinit = function fromEncinit(data) {
return new BIP151().fromEncinit(data);
return new BIP151().encinit(data);
};
BIP151.prototype.toEncack = function toEncack(writer) {
@ -158,24 +192,104 @@ BIP151.prototype.toEncack = function toEncack(writer) {
if (!writer)
p = p.render();
if (!this.ackSent) {
this.ackSent = true;
if (this.isReady()) {
this.handshake = true;
this.emit('handshake');
}
}
return p;
};
BIP151.prototype.toRekey = function toRekey(writer) {
var p = bcoin.writer(writer);
p.writeBytes(constants.ZERO_KEY);
if (!writer)
p = p.render();
return p;
};
BIP151.prototype.maybeRekey = function maybeRekey(data) {
var self = this;
this.processed += data.length;
if (this.processed >= this.highWaterMark) {
this.processed -= this.highWaterMark;
this.lastRekey = utils.ms();
utils.nextTick(function() {
self.rekey();
self.emit('rekey');
});
}
};
BIP151.prototype.complete = function complete(err) {
assert(!this.completed, 'Already completed.');
assert(this.callback, 'No completion callback.');
this.completed = true;
clearTimeout(this.timeout);
this.timeout = null;
this.callback(err);
this.callback = null;
};
BIP151.prototype.wait = function wait(timeout, callback) {
var self = this;
assert(!this.handshake, 'Cannot wait for init after handshake.');
this.callback = callback;
this.timeout = setTimeout(function() {
self.complete(new Error('Timed out.'));
}, 1000);
this.once('handshake', function() {
self.complete();
});
};
BIP151.prototype.encack = function encack(data) {
var p = bcoin.reader(data);
var publicKey = p.readBytes(33);
if (utils.isZero(publicKey)) {
assert(this.initSent, 'Unsolicited ACK.');
if (utils.equal(publicKey, constants.ZERO_KEY)) {
assert(this.ackReceived, 'No ACK before rekey.');
assert(this.handshake, 'No initialization before rekey.');
this.rekey();
return;
}
this.init(publicKey);
assert(!this.ackReceived, 'Already ACKed.');
this.ackReceived = true;
if (!this.initReceived) {
this.init(publicKey);
} else {
assert(utils.equal(publicKey, this.publicKey),
'Bad pubkey.');
}
if (this.isReady()) {
this.handshake = true;
this.emit('handshake');
}
};
BIP151.prototype.feed = function feed(data) {
var chunk, payload, tag, p, cmd, body;
this.maybeRekey(data);
while (data) {
if (!this.hasHeader) {
this.pendingHeaderTotal += data.length;
@ -236,10 +350,18 @@ BIP151.prototype.feed = function feed(data) {
}
p = bcoin.reader(payload, true);
cmd = p.readVarString('ascii');
body = p.readBytes(p.readU32());
this.emit('packet', cmd, body);
while (p.left()) {
try {
cmd = p.readVarString('ascii');
body = p.readBytes(p.readU32());
} catch (e) {
this.emit('error', e);
continue;
}
this.emit('packet', cmd, body);
}
}
};
@ -260,5 +382,9 @@ BIP151.prototype.frame = function frame(cmd, body) {
this.finish().copy(packet, 4 + payload.length);
this.sequence();
this.maybeRekey(payload);
return packet;
};
module.exports = BIP151;

136
test/bip151-test.js Normal file
View File

@ -0,0 +1,136 @@
'use strict';
var bn = require('bn.js');
var bcoin = require('../').set('main');
var utils = bcoin.utils;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var assert = require('assert');
describe('BIP151', function() {
var client = new bcoin.bip151();
var server = new bcoin.bip151();
var payload = new Buffer('deadbeef', 'hex');
it('should do encinit', function() {
client.encinit(server.toEncinit());
server.encinit(client.toEncinit());
assert(!client.handshake);
assert(!server.handshake);
});
it('should do encack', function() {
client.encack(server.toEncack());
server.encack(client.toEncack());
assert(client.handshake);
assert(server.handshake);
});
it('should have completed ECDH handshake', function() {
assert(client.isReady());
assert(server.isReady());
assert(client.handshake);
assert(server.handshake);
});
it('should encrypt payload from client to server', function() {
var packet = client.frame('fake', payload);
var emitted = false;
server.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
server.feed(packet);
assert(emitted);
});
it('should encrypt payload from server to client', function() {
var packet = server.frame('fake', payload);
var emitted = false;
client.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
client.feed(packet);
assert(emitted);
});
it('should encrypt payload from client to server (2)', function() {
var packet = client.frame('fake', payload);
var emitted = false;
server.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
server.feed(packet);
assert(emitted);
});
it('should encrypt payload from server to client (2)', function() {
var packet = server.frame('fake', payload);
var emitted = false;
client.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
client.feed(packet);
assert(emitted);
});
it('client should rekey', function() {
client.rekey();
server.encack(client.toRekey());
});
it('should encrypt payload from client to server after rekey', function() {
var packet = client.frame('fake', payload);
var emitted = false;
server.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
server.feed(packet);
assert(emitted);
});
it('should encrypt payload from server to client after rekey', function() {
var packet = server.frame('fake', payload);
var emitted = false;
client.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
client.feed(packet);
assert(emitted);
});
it('should encrypt payload from client to server after rekey (2)', function() {
var packet = client.frame('fake', payload);
var emitted = false;
server.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
server.feed(packet);
assert(emitted);
});
it('should encrypt payload from server to client after rekey (2)', function() {
var packet = server.frame('fake', payload);
var emitted = false;
client.once('packet', function(cmd, body) {
emitted = true;
assert.equal(cmd, 'fake');
assert.equal(body.toString('hex'), 'deadbeef');
});
client.feed(packet);
assert(emitted);
});
});