fcoin/lib/bcoin/bip151.js
Christopher Jeffrey adb7b28795
bip151: fix encack.
2016-06-27 07:13:38 -07:00

266 lines
5.8 KiB
JavaScript

/*!
* bip151.js - peer-to-peer communication encryption.
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var EventEmitter = require('events').EventEmitter;
var bcoin = require('./env');
var utils = require('./utils');
var constants = bcoin.protocol.constants;
var chachapoly = require('./chachapoly');
function BIP151(cipher, key) {
if (!(this instanceof BIP151))
return new BIP151(cipher, key);
EventEmitter.call(this);
this.publicKey = null;
this.privateKey = key || bcoin.ec.generatePrivateKey();
this.cipher = cipher || 0;
this.secret = null;
this.k1 = null;
this.k2 = null;
this.sid = null;
this.chacha = new chachapoly.ChaCha20();
this.aead = new chachapoly.AEAD();
this.mac = null;
this.tag = null;
this.seq = 0;
this.pendingHeader = [];
this.pendingHeaderTotal = 0;
this.hasHeader = false;
this.pending = [];
this.pendingTotal = 0;
this.waiting = 0;
}
utils.inherits(BIP151, EventEmitter);
BIP151.prototype.init = function init(publicKey) {
var p = bcoin.writer();
this.publicKey = publicKey;
this.secret = bcoin.ec.ecdh(this.publicKey, this.privateKey);
p.writeBytes(this.secret);
p.writeU8(this.cipher);
this.mac = utils.hmac('sha512', p.render(), 'encryption key');
this.k1 = this.mac.slice(0, 32);
this.k2 = this.mac.slice(32, 64);
this.sid = utils.hmac('sha256', this.secret, 'session id');
this.seq = 0;
this.chacha.init(this.k1, this.iv());
this.aead.init(this.k2, this.iv());
this.aead.aad(this.sid);
};
BIP151.prototype.rekey = function rekey() {
this.mac = utils.hash256(this.mac);
this.k1 = this.mac.slice(0, 32);
this.k2 = this.mac.slice(32, 64);
this.seq = 0;
this.chacha.init(this.k1, this.iv());
this.aead.init(this.k2, this.iv());
this.aead.aad(this.sid);
};
BIP151.prototype.sequence = function sequence() {
this.seq++;
this.chacha.init(this.k1, this.iv());
this.aead.init(this.k2, this.iv());
this.aead.aad(this.sid);
};
BIP151.prototype.iv = function iv() {
var p = bcoin.writer();
p.writeU64(this.seq);
p.writeU32(0);
return p.render();
};
BIP151.prototype.getPublicKey = function getPublicKey() {
return bcoin.ec.publicKeyCreate(this.privateKey, true);
};
BIP151.prototype.encryptSize = function encryptSize(size) {
var data = new Buffer(4);
data.writeUInt32LE(size, 0, true);
return this.chacha.encrypt(data);
};
BIP151.prototype.decryptSize = function decryptSize(data) {
data = data.slice(0, 4);
this.chacha.encrypt(data);
return data.readUInt32LE(0, true);
};
BIP151.prototype.encrypt = function encrypt(data) {
return this.aead.encrypt(data);
};
BIP151.prototype.decrypt = function decrypt(data) {
return this.aead.decrypt(data);
};
BIP151.prototype.finish = function finish(data) {
this.tag = this.aead.finish(data);
return this.tag;
};
BIP151.prototype.verify = function verify(tag) {
return chachapoly.Poly1305.verify(this.tag, tag);
};
BIP151.prototype.toEncinit = function toEncinit(writer) {
var p = bcoin.writer(writer);
p.writeBytes(this.getPublicKey());
p.writeU8(this.cipher);
if (!writer)
p = p.render();
return p;
};
BIP151.prototype.fromEncinit = function fromEncinit(data) {
var p = bcoin.reader(data);
var publicKey = p.readBytes(33);
this.cipher = p.readU8();
this.init(publicKey);
return this;
};
BIP151.fromEncinit = function fromEncinit(data) {
return new BIP151().fromEncinit(data);
};
BIP151.prototype.toEncack = function toEncack(writer) {
var p = bcoin.writer(writer);
p.writeBytes(this.getPublicKey());
if (!writer)
p = p.render();
return p;
};
BIP151.prototype.encack = function encack(data) {
var p = bcoin.reader(data);
var publicKey = p.readBytes(33);
var i;
for (i = 0; i < publicKey.length; i++) {
if (publicKey[i] !== 0)
break;
}
if (i === publicKey.length)
this.rekey();
else
this.init(publicKey);
return this;
};
BIP151.prototype.feed = function feed(data) {
var chunk, payload, tag, p, cmd, body;
while (data) {
if (!this.hasHeader) {
this.pendingHeaderTotal += data.length;
this.pendingHeader.push(data);
data = null;
if (this.pendingHeaderTotal < 4)
break;
chunk = Buffer.concat(this.pendingHeader);
this.pendingHeaderTotal = 0;
this.pendingHeader.length = 0;
this.waiting = this.decryptSize(chunk) + 16;
if (this.waiting - 32 > constants.MAX_MESSAGE) {
this.waiting = 0;
this.emit('error', new Error('Packet too large.'));
continue;
}
this.hasHeader = true;
data = chunk.slice(4);
if (data.length === 0)
break;
}
this.pendingTotal += data.length;
this.pending.push(data);
data = null;
if (this.pendingTotal < this.waiting)
break;
chunk = Buffer.concat(this.pending);
payload = chunk.slice(0, this.waiting - 16);
tag = chunk.slice(this.waiting - 16, this.waiting);
data = chunk.slice(this.waiting);
if (data.length === 0)
data = null;
this.decrypt(payload);
this.finish();
this.sequence();
this.pendingTotal = 0;
this.pending.length = 0;
this.hasHeader = false;
this.waiting = 0;
if (!this.verify(tag)) {
this.emit('error', new Error('Bad tag.'));
continue;
}
p = bcoin.reader(payload, true);
cmd = p.readVarString('ascii');
body = p.readBytes(p.readU32());
this.emit('packet', cmd, body);
}
};
BIP151.prototype.frame = function frame(cmd, body) {
var p = bcoin.writer();
var payload, packet;
p.writeVarString(cmd, 'ascii');
p.writeU32(body.length);
p.writeBytes(body);
payload = p.render();
packet = new Buffer(4 + payload.length + 16);
this.encryptSize(payload.length).copy(packet, 0);
this.encrypt(payload).copy(packet, 4);
this.finish().copy(packet, 4 + payload.length);
this.sequence();
return packet;
};