bip151: docs.

This commit is contained in:
Christopher Jeffrey 2016-07-26 14:07:06 -07:00
parent c085d446ab
commit 1c9d283f26
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -1,8 +1,13 @@
/*!
* bip151.js - peer-to-peer communication encryption.
* See: https://github.com/bitcoin/bips/blob/master/bip-0151.mediawiki
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* Copyright (c) 2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
* Resources:
* https://github.com/bitcoin/bips/blob/master/bip-0151.mediawiki
* https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.chacha20poly1305
* https://github.com/openssh/openssh-portable/blob/master/cipher-chachapoly.c
* https://github.com/openssh/openssh-portable/blob/master/cipher.c
* https://github.com/openssh/openssh-portable/blob/master/packet.c
*/
'use strict';
@ -14,11 +19,37 @@ var assert = utils.assert;
var constants = bcoin.protocol.constants;
var chachapoly = require('./chachapoly');
/*
* Constants
*/
var HKDF_SALT = new Buffer('bitcoinechd' /* ecHd (sic?) */, 'ascii');
var INFO_KEY1 = new Buffer('BitcoinK1', 'ascii');
var INFO_KEY2 = new Buffer('BitcoinK2', 'ascii');
var INFO_SID = new Buffer('BitcoinSessionID', 'ascii');
/**
* Represents a BIP151 input or output stream.
* @exports BIP151Stream
* @constructor
* @param {Number} cipher
* @param {Buffer?} key
* @property {Buffer} publicKey
* @property {Buffer} privateKey
* @property {Number} cipher
* @property {Buffer} prk
* @property {Buffer} k1
* @property {Buffer} k2
* @property {Buffer} sid
* @property {ChaCha20} chacha
* @property {AEAD} aead
* @property {Buffer} tag
* @property {Number} seq
* @property {Number} highWaterMark
* @property {Number} processed
* @property {Number} lastKey
*/
function BIP151Stream(cipher, key) {
if (!(this instanceof BIP151Stream))
return new BIP151Stream(cipher, key);
@ -55,6 +86,12 @@ function BIP151Stream(cipher, key) {
utils.inherits(BIP151Stream, EventEmitter);
/**
* Initialize the stream with peer's public key.
* Computes ecdh secret and chacha keys.
* @param {Buffer} publicKey
*/
BIP151Stream.prototype.init = function init(publicKey) {
var p = bcoin.writer();
@ -78,6 +115,12 @@ BIP151Stream.prototype.init = function init(publicKey) {
this.lastRekey = utils.now();
};
/**
* Add buffer size to `processed`,
* check whether we need to rekey.
* @param {Buffer} data
*/
BIP151Stream.prototype.maybeRekey = function maybeRekey(data) {
var now = utils.now();
@ -92,6 +135,11 @@ BIP151Stream.prototype.maybeRekey = function maybeRekey(data) {
}
};
/**
* Generate new chacha keys with `key = HASH256(key)`.
* This will reinitialize the state of both ciphers.
*/
BIP151Stream.prototype.rekey = function rekey() {
assert(this.prk, 'Cannot rekey before initialization.');
@ -105,6 +153,13 @@ BIP151Stream.prototype.rekey = function rekey() {
this.aead.aad(this.sid);
};
/**
* Increment packet sequence number and update IVs
* (note, sequence number overflows after 2^32-1).
* The IV will be updated without reinitializing
* cipher state.
*/
BIP151Stream.prototype.sequence = function sequence() {
this.seq++;
@ -119,6 +174,11 @@ BIP151Stream.prototype.sequence = function sequence() {
this.aead.aad(this.sid);
};
/**
* Render the IV necessary for cipher streams.
* @returns {Buffer}
*/
BIP151Stream.prototype.iv = function iv() {
var p = bcoin.writer();
p.writeU64(this.seq);
@ -126,39 +186,86 @@ BIP151Stream.prototype.iv = function iv() {
return p.render();
};
/**
* Get public key tied to private key
* (not the same as BIP151Stream#privateKey).
* @returns {Buffer}
*/
BIP151Stream.prototype.getPublicKey = function getPublicKey() {
return bcoin.ec.publicKeyCreate(this.privateKey, true);
};
/**
* Encrypt a payload size with k1.
* @param {Number} size
* @returns {Buffer}
*/
BIP151Stream.prototype.encryptSize = function encryptSize(size) {
var data = new Buffer(4);
data.writeUInt32LE(size, 0, true);
return this.chacha.encrypt(data);
};
/**
* Decrypt payload size with k1.
* @param {Buffer} data
* @returns {Number}
*/
BIP151Stream.prototype.decryptSize = function decryptSize(data) {
data = data.slice(0, 4);
this.chacha.encrypt(data);
return data.readUInt32LE(0, true);
};
/**
* Encrypt payload with AEAD (update cipher and mac).
* @param {Buffer} data
* @returns {Buffer} data
*/
BIP151Stream.prototype.encrypt = function encrypt(data) {
return this.aead.encrypt(data);
};
/**
* Decrypt payload with AEAD (update cipher and mac).
* @param {Buffer} data
* @returns {Buffer} data
*/
BIP151Stream.prototype.decrypt = function decrypt(data) {
return this.aead.decrypt(data);
};
/**
* Finalize AEAD and compute MAC.
* @returns {Buffer}
*/
BIP151Stream.prototype.finish = function finish() {
this.tag = this.aead.finish();
return this.tag;
};
/**
* Verify tag against mac in constant time.
* @param {Buffer} tag
* @returns {Boolean}
*/
BIP151Stream.prototype.verify = function verify(tag) {
return chachapoly.Poly1305.verify(this.tag, tag);
};
/**
* Parse a ciphertext payload chunk.
* Potentially emits a `packet` event.
* @param {Buffer} data
*/
BIP151Stream.prototype.feed = function feed(data) {
var chunk, payload, tag, p, cmd, body;
@ -241,7 +348,13 @@ BIP151Stream.prototype.feed = function feed(data) {
}
};
// TODO: We could batch packets here!
/**
* Frame and encrypt a plaintext payload.
* @param {String} cmd
* @param {Buffer} body
* @returns {Buffer} Ciphertext payload
*/
BIP151Stream.prototype.packet = function packet(cmd, body) {
var p = bcoin.writer();
var payload, packet;
@ -264,6 +377,24 @@ BIP151Stream.prototype.packet = function packet(cmd, body) {
return packet;
};
/**
* Represents a BIP151 input and output stream.
* Holds state for peer communication.
* @exports BIP151
* @constructor
* @param {Number} cipher
* @property {BIP151Stream} input
* @property {BIP151Stream} output
* @property {Boolean} initReceived
* @property {Boolean} ackReceived
* @property {Boolean} initSent
* @property {Boolean} ackSent
* @property {Object} timeout
* @property {Function} callback
* @property {Boolean} completed
* @property {Boolean} handshake
*/
function BIP151(cipher) {
if (!(this instanceof BIP151))
return new BIP151(cipher);
@ -287,6 +418,11 @@ function BIP151(cipher) {
utils.inherits(BIP151, EventEmitter);
/**
* Initialize BIP151. Bind to events.
* @private
*/
BIP151.prototype._init = function _init() {
var self = this;
@ -299,6 +435,11 @@ BIP151.prototype._init = function _init() {
});
};
/**
* Test whether handshake has completed.
* @returns {Boolean}
*/
BIP151.prototype.isReady = function isReady() {
return this.initSent
&& this.ackReceived
@ -306,6 +447,12 @@ BIP151.prototype.isReady = function isReady() {
&& this.ackSent;
};
/**
* Render an `encinit` packet. Contains the
* input public key and cipher number.
* @returns {Buffer}
*/
BIP151.prototype.toEncinit = function toEncinit(writer) {
var p = bcoin.writer(writer);
@ -320,6 +467,11 @@ BIP151.prototype.toEncinit = function toEncinit(writer) {
return p;
};
/**
* Handle `encack` from remote peer.
* @param {Buffer} data
*/
BIP151.prototype.encack = function encack(data) {
var p = bcoin.reader(data);
var publicKey = p.readBytes(33);
@ -343,6 +495,11 @@ BIP151.prototype.encack = function encack(data) {
}
};
/**
* Handle `encinit` from remote peer.
* @param {Buffer}
*/
BIP151.prototype.encinit = function encinit(data) {
var p = bcoin.reader(data);
var publicKey = p.readBytes(33);
@ -356,6 +513,12 @@ BIP151.prototype.encinit = function encinit(data) {
this.output.init(publicKey);
};
/**
* Render `encack` packet. Contains the
* output stream public key.
* @returns {Buffer}
*/
BIP151.prototype.toEncack = function toEncack(writer) {
var p = bcoin.writer(writer);
@ -377,6 +540,13 @@ BIP151.prototype.toEncack = function toEncack(writer) {
return p;
};
/**
* Render `encack` packet with an all
* zero public key, notifying of a rekey
* for the output stream.
* @returns {Buffer}
*/
BIP151.prototype.toRekey = function toRekey(writer) {
var p = bcoin.writer(writer);
@ -388,6 +558,12 @@ BIP151.prototype.toRekey = function toRekey(writer) {
return p;
};
/**
* Complete the timeout for handshake,
* possibly with an error.
* @param {Error?} err
*/
BIP151.prototype.complete = function complete(err) {
assert(!this.completed, 'Already completed.');
assert(this.callback, 'No completion callback.');
@ -401,6 +577,12 @@ BIP151.prototype.complete = function complete(err) {
this.callback = null;
};
/**
* Set a timeout and wait for handshake to complete.
* @param {Number} timeout - Timeout in ms.
* @param {Function} callback
*/
BIP151.prototype.wait = function wait(timeout, callback) {
var self = this;
@ -417,12 +599,30 @@ BIP151.prototype.wait = function wait(timeout, callback) {
});
};
/**
* Feed ciphertext payload chunk
* to the input stream. Potentially
* emits a `packet` event.
* @param {Buffer} data
*/
BIP151.prototype.feed = function feed(data) {
return this.input.feed(data);
};
/**
* Frame plaintext payload for the output stream.
* @param {String} cmd
* @param {Buffer} body
* @returns {Buffer} Ciphertext payload
*/
BIP151.prototype.packet = function packet(cmd, body) {
return this.output.packet(cmd, body);
};
/*
* Expose
*/
module.exports = BIP151;