bip151: docs.
This commit is contained in:
parent
c085d446ab
commit
1c9d283f26
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user