bip151 & bip152: preliminary implementations.

This commit is contained in:
Christopher Jeffrey 2016-06-27 01:07:53 -07:00
parent 5973cad305
commit 70b83f22fa
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 834 additions and 0 deletions

266
lib/bcoin/bip151.js Normal file
View File

@ -0,0 +1,266 @@
/*!
* 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 assert = utils.assert;
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 b = 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.init(publicKey);
else
this.rekey();
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;
};

568
lib/bcoin/bip152.js Normal file
View File

@ -0,0 +1,568 @@
/*!
* bip152.js - compact block object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var bcoin = require('./env');
var utils = require('./utils');
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var siphash = require('./siphash');
/**
* Represents a compact block (bip152): `cmpctblock` packet.
* @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki
* @exports CompactBlock
* @constructor
* @extends AbstractBlock
* @param {NakedBlock} options
* @property {TX[]} available - Available transaction vector.
*/
function CompactBlock(options) {
if (!(this instanceof CompactBlock))
return new CompactBlock(options);
bcoin.abstractblock.call(this, options);
this.keyNonce = null;
this.ids = [];
this.ptx = [];
this.available = [];
this.idMap = {};
this.count = 0;
this.k0 = null;
this.k1 = null;
if (options)
this.fromOptions(options);
}
utils.inherits(CompactBlock, bcoin.abstractblock);
CompactBlock.prototype._verify = function _verify(ret) {
return this.verifyHeaders(ret);
};
CompactBlock.prototype.fromOptions = function fromOptions(options) {
this.keyNonce = options.keyNonce;
this.ids = options.ids || [];
this.ptx = options.ptx || [];
this.available = options.available || [];
this.idMap = options.idMap || {};
this.count = options.count || 0;
this.k0 = options.k0;
this.k1 = options.k1;
this.initKey();
this.init();
return this;
};
CompactBlock.fromOptions = function fromOptions(options) {
return new CompactBlock().fromOptions(options);
};
CompactBlock.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
var last = 0;
var i, count, index, tx;
this.version = p.readU32(); // Technically signed
this.prevBlock = p.readHash('hex');
this.merkleRoot = p.readHash('hex');
this.ts = p.readU32();
this.bits = p.readU32();
this.nonce = p.readU32();
this.keyNonce = p.readU64();
this.initKey();
count = p.readVarint2();
this.totalTX += count;
for (i = 0; i < count; i++)
this.ids.push(p.readU32() + p.readU16() * 0x100000000);
count = p.readVarint2();
this.totalTX += count;
for (i = 0; i < count; i++) {
index = p.readVarint2();
assert(index <= 0xffff);
assert(index < this.totalTX);
tx = bcoin.tx.fromRaw(p);
this.ptx.push([index, tx]);
}
this.init();
return this;
};
CompactBlock.fromRaw = function fromRaw(data) {
return new CompactBlock().fromRaw(data);
};
CompactBlock.prototype.toRaw = function toRaw(witness, writer) {
var p = bcoin.writer(writer);
var i, id, lo, hi, ptx;
p.write32(this.version);
p.writeHash(this.prevBlock);
p.writeHash(this.merkleRoot);
p.writeU32(this.ts);
p.writeU32(this.bits);
p.writeU32(this.nonce);
p.writeU64(this.keyNonce);
p.writeVarint2(this.ids.length);
for (i = 0; i < this.ids.length; i++) {
id = this.ids[i];
lo = id % 0x100000000;
hi = (id - lo) / 0x100000000;
hi &= 0xffff;
p.writeU32(lo);
p.writeU16(hi);
}
p.writeVarint2(this.ptx.length);
for (i = 0; i < this.ptx.length; i++) {
ptx = this.ptx[i];
p.writeVarint2(ptx[0]);
if (!witness)
ptx[1].toNormal(p);
else
ptx[1].toRaw(p);
}
if (!writer)
p = p.render();
return p;
};
CompactBlock.prototype.toRequest = function toRequest() {
return BlockTXRequest.fromCompact(this);
};
CompactBlock.prototype.fillMempool = function fillMempool(mempool, callback) {
var self = this;
var have = {};
var id, index;
mempool.getSnapshot(function(err, hashes) {
if (err)
return callback(err);
utils.forEachSerial(hashes, function(hash, next) {
id = self.sid(hash);
index = self.idMap[id];
if (index == null)
return next();
if (have[index]) {
// Siphash collision, just request it.
self.available[index] = null;
self.count--;
return next();
}
mempool.getTX(hash, function(err, tx) {
if (err)
return callback(err);
// Race condition: tx
// fell out of mempool.
if (!tx)
return next();
self.available[index] = tx;
have[index] = true;
self.count++;
// We actually may have a siphash collision
// here, but exit early anyway for perf.
if (self.count === self.totalTX)
return callback(null, true);
next();
});
}, callback);
});
};
CompactBlock.prototype.fillMissing = function fillMissing(missing) {
var offset = 0;
var i, tx;
for (i = 0; i < this.available.length; i++) {
if (this.available[i])
continue;
if (offset >= missing.length)
return false;
this.available = missing[offset++];
}
return offset === missing.length;
};
CompactBlock.prototype.sid = function sid(hash) {
if (typeof hash === 'string')
hash = new Buffer(hash, 'hex');
hash = siphash(hash, this.k0, this.k1);
return hash.readUInt32LE(2, true)
+ hash.readUInt16LE(6, true)
* 0x100000000;
};
CompactBlock.prototype.hasIndex = function hasIndex(index) {
return this.available[index] != null;
};
CompactBlock.prototype.initKey = function initKey() {
var nonce = this.keyNonce.toArrayLike(Buffer, 'be', 8);
var data = Buffer.concat([this.abbr(), nonce]);
var hash = utils.sha256(data);
this.k0 = hash.slice(0, 8);
this.k1 = hash.slice(8, 16);
};
CompactBlock.prototype.init = function init() {
var i, last, ptx, offset;
if (this.totalTX === 0)
throw new Error('Empty vectors.');
if (this.totalTX > constants.block.MAX_SIZE / 10)
throw new Error('Compact block too big.');
// No sparse arrays here, v8.
for (i = 0; i < this.totalTX; i++)
this.available.push(null);
last = -1;
for (i = 0; i < this.ptx.length; i++) {
ptx = this.ptx[i];
assert(ptx);
last += ptx[0] + 1;
assert(last <= 0xffff);
assert(last <= this.ids.length + i);
this.available[last] = ptx[1];
this.count++;
}
offset = 0;
for (i = 0; i < this.ids.length; i++) {
while (this.available[i + offset])
offset++;
this.idMap[this.ids[i]] = i + offset;
// We're supposed to fail here if there's
// more than 12 hash collisions, but we
// don't have lowlevel access to our hash
// table. Hopefully we don't get hashdos'd.
}
// Fails on siphash collision
assert(this.ids.length === Object.keys(this.idMap).length);
};
CompactBlock.prototype.toBlock = function toBlock() {
var block = new bcoin.block();
var i, tx;
block.version = this.version;
block.prevBlock = this.prevBlock;
block.merkleRoot = this.merkleRoot;
block.ts = this.ts;
block.bits = this.bits;
block.nonce = this.nonce;
block.totalTX = this.totalTX;
block.txs = new Array(this.ptx.length);
block._hash = this._hash;
block._valid = this._valid;
for (i = 0; i < this.available.length; i++) {
tx = this.available[i];
assert(tx, 'Compact block is not full.');
tx.setBlock(block, i);
block.txs[i] = tx;
}
return block;
};
CompactBlock.prototype.fromBlock = function fromBlock(block) {
var i, tx, id;
this.version = block.version;
this.prevBlock = block.prevBlock;
this.merkleRoot = block.merkleRoot;
this.ts = block.ts;
this.bits = block.bits;
this.nonce = block.nonce;
this.totalTX = block.totalTX;
this.keyNonce = utils.nonce();
this.initKey();
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
id = this.sid(tx.hash());
this.ids.push(id);
}
this.ptx.push([0, block.txs[0]]);
return this;
};
CompactBlock.fromBlock = function fromBlock(block) {
return new CompactBlock().fromBlock(block);
};
/**
* Represents a BlockTransactionsRequest (bip152): `getblocktxn` packet.
* @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki
* @constructor
*/
function BlockTXRequest(options) {
if (!(this instanceof BlockTXRequest))
return new BlockTXRequest(options);
this.hash = null;
this.indexes = [];
if (options)
this.fromOptions(options);
}
BlockTXRequest.prototype.fromOptions = function fromOptions(options) {
this.hash = options.hash;
this.indexes = options.indexes || [];
return this;
};
BlockTXRequest.fromOptions = function fromOptions(options) {
return new BlockTXRequest().fromOptions(options);
};
BlockTXRequest.prototype.fromCompact = function fromCompact(block) {
var i;
this.hash = block.hash('hex');
for (i = 0; i < block.available.length; i++) {
if (!block.available[i])
this.indexes.push(i);
}
return this;
};
BlockTXRequest.fromCompact = function fromCompact(block) {
return new BlockTXRequest().fromCompact(block);
};
BlockTXRequest.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
var i, count, index, offset;
this.hash = p.readHash('hex');
count = p.readVarint2();
for (i = 0; i < count; i++) {
index = p.readVarint2();
assert(index <= 0xffff);
this.indexes.push(index);
}
offset = 0;
for (i = 0; i < count; i++) {
index = this.indexes[i];
index += offset;
assert(index <= 0xffff);
this.indexes[i] = index;
offset = index + 1;
}
return this;
};
BlockTXRequest.fromRaw = function fromRaw(data) {
return new BlockTXRequest().fromRaw(data);
};
BlockTXRequest.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
var i, index;
p.writeHash(this.hash);
p.writeVarint2(this.indexes.length);
for (i = 0; i < this.indexes.length; i++) {
index = this.indexes[i] - (i === 0 ? 0 : this.indexes[i - 1] + 1);
p.writeVarint2(index);
}
if (!writer)
p = p.render();
return p;
};
/**
* Represents BlockTransactions (bip152): `blocktxn` packet.
* @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki
* @constructor
*/
function BlockTX() {
if (!(this instanceof BlockTX))
return new BlockTX(options);
this.hash = null;
this.txs = [];
if (options)
this.fromOptions(options);
}
BlockTX.prototype.fromOptions = function fromOptions(options) {
this.hash = options.hash;
this.txs = options.txs || [];
return this;
};
BlockTX.fromOptions = function fromOptions(options) {
return new BlockTX().fromOptions(options);
};
BlockTX.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
var i, count;
this.hash = p.readHash('hex');
count = p.readVarint2();
for (i = 0; i < count; i++)
this.txs.push(bcoin.tx.fromRaw(p));
return this;
};
BlockTX.fromRaw = function fromRaw(data) {
return new BlockTX().fromRaw(data);
};
BlockTX.prototype.fromBlock = function fromBlock(block, request) {
var i, index;
this.hash = request.hash;
for (i = 0; i < request.indexes.length; i++) {
index = request.indexes[i];
if (index >= block.txs.length)
return;
this.txs.push(block.txs[index]);
}
return this;
};
BlockTX.fromBlock = function fromBlock(block, request) {
return new BlockTX().fromBlock(block, request);
};
BlockTX.prototype.toRaw = function toRaw(witness, writer) {
var p = bcoin.writer(writer);
var i, tx;
p.writeHash(this.hash);
p.writeVarint2(this.txs.length);
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
if (!witness)
tx.toNormal(p);
else
tx.toRaw(p);
}
if (!writer)
p = p.render();
return p;
};
/**
* Represents a SendCompact message (bip152): `sendcmpct` packet.
* @see https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki
* @constructor
*/
// NOTE TO SELF: Protocol version >= 70014
function SendCompact(mode, version) {
if (!(this instanceof SendCompact))
return new SendCompact(mode, version);
this.mode = mode || 0;
this.version = version || 1;
}
SendCompact.prototype.fromRaw = function fromRaw(data) {
var p = bcoin.reader(data);
this.mode = p.readU8();
this.version = p.readU53();
return this;
};
SendCompact.fromRaw = function fromRaw(data) {
return new SendCompact().fromRaw(data);
};
SendCompact.prototype.toRaw = function toRaw(writer) {
var p = bcoin.writer(writer);
p.writeU8(this.mode);
p.writeU64(this.version);
if (!writer)
p = p.render();
return p;
};
/*
* Expose
*/
exports.CompactBlock = CompactBlock;
exports.BlockTXRequest = BlockTXRequest;
exports.BlockTX = BlockTX;
exports.SendCompact = SendCompact;