net: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 19:37:09 -08:00
parent 8be995bd78
commit 4ebfb5d9ff
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 9518 additions and 9467 deletions

View File

@ -26,14 +26,10 @@ const packets = require('./packets');
const {encoding} = bio; const {encoding} = bio;
/** /**
* BIP150
* Represents a BIP150 input/output stream. * Represents a BIP150 input/output stream.
* @alias module:net.BIP150 * @alias module:net.BIP150
* @constructor * @extends EventEmitter
* @param {BIP151} bip151
* @param {String} host
* @param {Boolean} outbound
* @param {AuthDB} db
* @param {Buffer} key - Identity key.
* @property {BIP151} bip151 * @property {BIP151} bip151
* @property {BIP151Stream} input * @property {BIP151Stream} input
* @property {BIP151Stream} output * @property {BIP151Stream} output
@ -51,11 +47,19 @@ const {encoding} = bio;
* @property {Boolean} completed * @property {Boolean} completed
*/ */
function BIP150(bip151, host, outbound, db, key) { class BIP150 extends EventEmitter {
if (!(this instanceof BIP150)) /**
return new BIP150(bip151, host, outbound, db, key); * Create a BIP150 input/output stream.
* @constructor
* @param {BIP151} bip151
* @param {String} host
* @param {Boolean} outbound
* @param {AuthDB} db
* @param {Buffer} key - Identity key.
*/
EventEmitter.call(this); constructor(bip151, host, outbound, db, key) {
super();
assert(bip151, 'BIP150 requires BIP151.'); assert(bip151, 'BIP150 requires BIP151.');
assert(typeof host === 'string', 'Hostname required.'); assert(typeof host === 'string', 'Hostname required.');
@ -84,34 +88,32 @@ function BIP150(bip151, host, outbound, db, key) {
this.onAuth = null; this.onAuth = null;
this.init(); this.init();
} }
Object.setPrototypeOf(BIP150.prototype, EventEmitter.prototype); /**
/**
* Initialize BIP150. * Initialize BIP150.
* @private * @private
*/ */
BIP150.prototype.init = function init() { init() {
if (this.outbound) if (this.outbound)
this.peerIdentity = this.db.getKnown(this.hostname); this.peerIdentity = this.db.getKnown(this.hostname);
}; }
/** /**
* Test whether the state should be * Test whether the state should be
* considered authed. This differs * considered authed. This differs
* for inbound vs. outbound. * for inbound vs. outbound.
* @returns {Boolean} * @returns {Boolean}
*/ */
BIP150.prototype.isAuthed = function isAuthed() { isAuthed() {
if (this.outbound) if (this.outbound)
return this.challengeSent && this.challengeReceived; return this.challengeSent && this.challengeReceived;
return this.challengeReceived && this.replyReceived; return this.challengeReceived && this.replyReceived;
}; }
/** /**
* Handle a received challenge hash. * Handle a received challenge hash.
* Returns an authreply signature. * Returns an authreply signature.
* @param {Buffer} hash * @param {Buffer} hash
@ -119,7 +121,7 @@ BIP150.prototype.isAuthed = function isAuthed() {
* @throws on auth failure * @throws on auth failure
*/ */
BIP150.prototype.challenge = function challenge(hash) { challenge(hash) {
const type = this.outbound ? 'r' : 'i'; const type = this.outbound ? 'r' : 'i';
assert(this.bip151.handshake, 'No BIP151 handshake before challenge.'); assert(this.bip151.handshake, 'No BIP151 handshake before challenge.');
@ -143,9 +145,9 @@ BIP150.prototype.challenge = function challenge(hash) {
// authreply // authreply
return secp256k1.fromDER(sig); return secp256k1.fromDER(sig);
}; }
/** /**
* Handle a received reply signature. * Handle a received reply signature.
* Returns an authpropose hash. * Returns an authpropose hash.
* @param {Buffer} data * @param {Buffer} data
@ -153,7 +155,7 @@ BIP150.prototype.challenge = function challenge(hash) {
* @throws on auth failure * @throws on auth failure
*/ */
BIP150.prototype.reply = function reply(data) { reply(data) {
const type = this.outbound ? 'i' : 'r'; const type = this.outbound ? 'i' : 'r';
assert(this.challengeSent, 'Unsolicited reply.'); assert(this.challengeSent, 'Unsolicited reply.');
@ -184,16 +186,16 @@ BIP150.prototype.reply = function reply(data) {
// authpropose // authpropose
return this.hash(this.input.sid, 'p', this.publicKey); return this.hash(this.input.sid, 'p', this.publicKey);
}; }
/** /**
* Handle a received propose hash. * Handle a received propose hash.
* Returns an authchallenge hash. * Returns an authchallenge hash.
* @param {Buffer} hash * @param {Buffer} hash
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP150.prototype.propose = function propose(hash) { propose(hash) {
assert(!this.outbound, 'Outbound peer tried to propose.'); assert(!this.outbound, 'Outbound peer tried to propose.');
assert(!this.challengeSent, 'Unsolicited propose.'); assert(!this.challengeSent, 'Unsolicited propose.');
assert(!this.proposeReceived, 'Peer proposed twice.'); assert(!this.proposeReceived, 'Peer proposed twice.');
@ -213,16 +215,16 @@ BIP150.prototype.propose = function propose(hash) {
// authchallenge // authchallenge
return this.hash(this.output.sid, 'r', this.peerIdentity); return this.hash(this.output.sid, 'r', this.peerIdentity);
}; }
/** /**
* Create initial authchallenge hash * Create initial authchallenge hash
* for the peer. The peer's identity * for the peer. The peer's identity
* key must be known. * key must be known.
* @returns {AuthChallengePacket} * @returns {AuthChallengePacket}
*/ */
BIP150.prototype.toChallenge = function toChallenge() { toChallenge() {
assert(this.bip151.handshake, 'No BIP151 handshake before challenge.'); assert(this.bip151.handshake, 'No BIP151 handshake before challenge.');
assert(this.outbound, 'Cannot challenge an inbound connection.'); assert(this.outbound, 'Cannot challenge an inbound connection.');
assert(this.peerIdentity, 'Cannot challenge without a peer identity.'); assert(this.peerIdentity, 'Cannot challenge without a peer identity.');
@ -233,9 +235,9 @@ BIP150.prototype.toChallenge = function toChallenge() {
this.challengeSent = true; this.challengeSent = true;
return new packets.AuthChallengePacket(msg); return new packets.AuthChallengePacket(msg);
}; }
/** /**
* Derive new cipher keys based on * Derive new cipher keys based on
* BIP150 data. This differs from * BIP150 data. This differs from
* the regular key derivation of BIP151. * the regular key derivation of BIP151.
@ -246,44 +248,44 @@ BIP150.prototype.toChallenge = function toChallenge() {
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP150.prototype.rekey = function rekey(sid, key, req, res) { rekey(sid, key, req, res) {
const seed = Buffer.allocUnsafe(130); const seed = Buffer.allocUnsafe(130);
sid.copy(seed, 0); sid.copy(seed, 0);
key.copy(seed, 32); key.copy(seed, 32);
req.copy(seed, 64); req.copy(seed, 64);
res.copy(seed, 97); res.copy(seed, 97);
return hash256.digest(seed); return hash256.digest(seed);
}; }
/** /**
* Rekey the BIP151 input stream * Rekey the BIP151 input stream
* using BIP150-style derivation. * using BIP150-style derivation.
*/ */
BIP150.prototype.rekeyInput = function rekeyInput() { rekeyInput() {
const stream = this.input; const stream = this.input;
const req = this.peerIdentity; const req = this.peerIdentity;
const res = this.publicKey; const res = this.publicKey;
const k1 = this.rekey(stream.sid, stream.k1, req, res); const k1 = this.rekey(stream.sid, stream.k1, req, res);
const k2 = this.rekey(stream.sid, stream.k2, req, res); const k2 = this.rekey(stream.sid, stream.k2, req, res);
stream.rekey(k1, k2); stream.rekey(k1, k2);
}; }
/** /**
* Rekey the BIP151 output stream * Rekey the BIP151 output stream
* using BIP150-style derivation. * using BIP150-style derivation.
*/ */
BIP150.prototype.rekeyOutput = function rekeyOutput() { rekeyOutput() {
const stream = this.output; const stream = this.output;
const req = this.publicKey; const req = this.publicKey;
const res = this.peerIdentity; const res = this.peerIdentity;
const k1 = this.rekey(stream.sid, stream.k1, req, res); const k1 = this.rekey(stream.sid, stream.k1, req, res);
const k2 = this.rekey(stream.sid, stream.k2, req, res); const k2 = this.rekey(stream.sid, stream.k2, req, res);
stream.rekey(k1, k2); stream.rekey(k1, k2);
}; }
/** /**
* Create a hash using the session ID. * Create a hash using the session ID.
* @param {Buffer} sid * @param {Buffer} sid
* @param {String} ch * @param {String} ch
@ -291,15 +293,15 @@ BIP150.prototype.rekeyOutput = function rekeyOutput() {
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP150.prototype.hash = function hash(sid, ch, key) { hash(sid, ch, key) {
const data = Buffer.allocUnsafe(66); const data = Buffer.allocUnsafe(66);
sid.copy(data, 0); sid.copy(data, 0);
data[32] = ch.charCodeAt(0); data[32] = ch.charCodeAt(0);
key.copy(data, 33); key.copy(data, 33);
return hash256.digest(data); return hash256.digest(data);
}; }
/** /**
* Find an authorized peer in the Auth * Find an authorized peer in the Auth
* DB based on a proposal hash. Note * DB based on a proposal hash. Note
* that the hash to find is specific * that the hash to find is specific
@ -309,7 +311,7 @@ BIP150.prototype.hash = function hash(sid, ch, key) {
* @returns {Buffer|null} * @returns {Buffer|null}
*/ */
BIP150.prototype.findAuthorized = function findAuthorized(hash) { findAuthorized(hash) {
// Scary O(n) stuff. // Scary O(n) stuff.
for (const key of this.db.authorized) { for (const key of this.db.authorized) {
const msg = this.hash(this.output.sid, 'p', key); const msg = this.hash(this.output.sid, 'p', key);
@ -322,27 +324,27 @@ BIP150.prototype.findAuthorized = function findAuthorized(hash) {
} }
return null; return null;
}; }
/** /**
* Destroy the BIP150 stream and * Destroy the BIP150 stream and
* any current running wait job. * any current running wait job.
*/ */
BIP150.prototype.destroy = function destroy() { destroy() {
if (!this.job) if (!this.job)
return; return;
this.reject(new Error('BIP150 stream was destroyed.')); this.reject(new Error('BIP150 stream was destroyed.'));
}; }
/** /**
* Cleanup wait job. * Cleanup wait job.
* @private * @private
* @returns {Job} * @returns {Job}
*/ */
BIP150.prototype.cleanup = function cleanup() { cleanup() {
const job = this.job; const job = this.job;
assert(!this.completed, 'Already completed.'); assert(!this.completed, 'Already completed.');
@ -362,43 +364,43 @@ BIP150.prototype.cleanup = function cleanup() {
} }
return job; return job;
}; }
/** /**
* Resolve the current wait job. * Resolve the current wait job.
* @private * @private
* @param {Object} result * @param {Object} result
*/ */
BIP150.prototype.resolve = function resolve(result) { resolve(result) {
const job = this.cleanup(); const job = this.cleanup();
job.resolve(result); job.resolve(result);
}; }
/** /**
* Reject the current wait job. * Reject the current wait job.
* @private * @private
* @param {Error} err * @param {Error} err
*/ */
BIP150.prototype.reject = function reject(err) { reject(err) {
const job = this.cleanup(); const job = this.cleanup();
job.reject(err); job.reject(err);
}; }
/** /**
* Wait for handshake to complete. * Wait for handshake to complete.
* @param {Number} timeout * @param {Number} timeout
* @returns {Promise} * @returns {Promise}
*/ */
BIP150.prototype.wait = function wait(timeout) { wait(timeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._wait(timeout, resolve, reject); this._wait(timeout, resolve, reject);
}); });
}; }
/** /**
* Wait for handshake to complete. * Wait for handshake to complete.
* @private * @private
* @param {Number} timeout * @param {Number} timeout
@ -406,7 +408,7 @@ BIP150.prototype.wait = function wait(timeout) {
* @param {Function} reject * @param {Function} reject
*/ */
BIP150.prototype._wait = function _wait(timeout, resolve, reject) { _wait(timeout, resolve, reject) {
assert(!this.auth, 'Cannot wait for init after handshake.'); assert(!this.auth, 'Cannot wait for init after handshake.');
this.job = { resolve, reject }; this.job = { resolve, reject };
@ -422,44 +424,47 @@ BIP150.prototype._wait = function _wait(timeout, resolve, reject) {
this.onAuth = this.resolve.bind(this); this.onAuth = this.resolve.bind(this);
this.once('auth', this.onAuth); this.once('auth', this.onAuth);
}; }
/** /**
* Serialize the peer's identity * Serialize the peer's identity
* key as a BIP150 "address". * key as a BIP150 "address".
* @returns {Base58String} * @returns {Base58String}
*/ */
BIP150.prototype.getAddress = function getAddress() { getAddress() {
assert(this.peerIdentity, 'Cannot serialize address.'); assert(this.peerIdentity, 'Cannot serialize address.');
return BIP150.address(this.peerIdentity); return BIP150.address(this.peerIdentity);
}; }
/** /**
* Serialize an identity key as a * Serialize an identity key as a
* BIP150 "address". * BIP150 "address".
* @returns {Base58String} * @returns {Base58String}
*/ */
BIP150.address = function address(key) { static address(key) {
const bw = bio.write(27); const bw = bio.write(27);
bw.writeU8(0x0f); bw.writeU8(0x0f);
bw.writeU16BE(0xff01); bw.writeU16BE(0xff01);
bw.writeBytes(hash160.digest(key)); bw.writeBytes(hash160.digest(key));
bw.writeChecksum(hash256.digest); bw.writeChecksum(hash256.digest);
return base58.encode(bw.render()); return base58.encode(bw.render());
}; }
}
/** /**
* AuthDB * AuthDB
* @alias module:net.AuthDB * @alias module:net.AuthDB
*/
class AuthDB {
/**
* Create an auth DB.
* @constructor * @constructor
*/ */
function AuthDB(options) { constructor(options) {
if (!(this instanceof AuthDB))
return new AuthDB(options);
this.logger = Logger.global; this.logger = Logger.global;
this.resolve = dns.lookup; this.resolve = dns.lookup;
this.prefix = null; this.prefix = null;
@ -469,14 +474,14 @@ function AuthDB(options) {
this.authorized = []; this.authorized = [];
this.init(options); this.init(options);
} }
/** /**
* Initialize authdb with options. * Initialize authdb with options.
* @param {Object} options * @param {Object} options
*/ */
AuthDB.prototype.init = function init(options) { init(options) {
if (!options) if (!options)
return; return;
@ -504,37 +509,37 @@ AuthDB.prototype.init = function init(options) {
assert(typeof options.prefix === 'string'); assert(typeof options.prefix === 'string');
this.prefix = options.prefix; this.prefix = options.prefix;
} }
}; }
/** /**
* Open auth database (lookup known peers). * Open auth database (lookup known peers).
* @method * @method
* @returns {Promise} * @returns {Promise}
*/ */
AuthDB.prototype.open = async function open() { async open() {
await this.readKnown(); await this.readKnown();
await this.readAuth(); await this.readAuth();
await this.lookup(); await this.lookup();
}; }
/** /**
* Close auth database. * Close auth database.
* @method * @method
* @returns {Promise} * @returns {Promise}
*/ */
AuthDB.prototype.close = async function close() { async close() {
; ;
}; }
/** /**
* Add a known peer. * Add a known peer.
* @param {String} host - Peer Hostname * @param {String} host - Peer Hostname
* @param {Buffer} key - Identity Key * @param {Buffer} key - Identity Key
*/ */
AuthDB.prototype.addKnown = function addKnown(host, key) { addKnown(host, key) {
assert(typeof host === 'string', assert(typeof host === 'string',
'Known host must be a string.'); 'Known host must be a string.');
@ -550,52 +555,52 @@ AuthDB.prototype.addKnown = function addKnown(host, key) {
} }
this.known.set(host, key); this.known.set(host, key);
}; }
/** /**
* Add an authorized peer. * Add an authorized peer.
* @param {Buffer} key - Identity Key * @param {Buffer} key - Identity Key
*/ */
AuthDB.prototype.addAuthorized = function addAuthorized(key) { addAuthorized(key) {
assert(Buffer.isBuffer(key) && key.length === 33, assert(Buffer.isBuffer(key) && key.length === 33,
'Invalid public key for authorized peer.'); 'Invalid public key for authorized peer.');
this.authorized.push(key); this.authorized.push(key);
}; }
/** /**
* Initialize known peers with a host->key map. * Initialize known peers with a host->key map.
* @param {Object} map * @param {Object} map
*/ */
AuthDB.prototype.setKnown = function setKnown(map) { setKnown(map) {
this.known.clear(); this.known.clear();
for (const host of Object.keys(map)) { for (const host of Object.keys(map)) {
const key = map[host]; const key = map[host];
this.addKnown(host, key); this.addKnown(host, key);
} }
}; }
/** /**
* Initialize authorized peers with a list of keys. * Initialize authorized peers with a list of keys.
* @param {Buffer[]} keys * @param {Buffer[]} keys
*/ */
AuthDB.prototype.setAuthorized = function setAuthorized(keys) { setAuthorized(keys) {
this.authorized.length = 0; this.authorized.length = 0;
for (const key of keys) for (const key of keys)
this.addAuthorized(key); this.addAuthorized(key);
}; }
/** /**
* Get a known peer key by hostname. * Get a known peer key by hostname.
* @param {String} hostname * @param {String} hostname
* @returns {Buffer|null} * @returns {Buffer|null}
*/ */
AuthDB.prototype.getKnown = function getKnown(hostname) { getKnown(hostname) {
const known = this.known.get(hostname); const known = this.known.get(hostname);
if (known) if (known)
@ -604,24 +609,24 @@ AuthDB.prototype.getKnown = function getKnown(hostname) {
const addr = IP.fromHostname(hostname); const addr = IP.fromHostname(hostname);
return this.known.get(addr.host); return this.known.get(addr.host);
}; }
/** /**
* Lookup known peers. * Lookup known peers.
* @method * @method
* @returns {Promise} * @returns {Promise}
*/ */
AuthDB.prototype.lookup = async function lookup() { async lookup() {
const jobs = []; const jobs = [];
for (const [addr, key] of this.dnsKnown) for (const [addr, key] of this.dnsKnown)
jobs.push(this.populate(addr, key)); jobs.push(this.populate(addr, key));
await Promise.all(jobs); await Promise.all(jobs);
}; }
/** /**
* Populate known peers with hosts. * Populate known peers with hosts.
* @method * @method
* @private * @private
@ -630,7 +635,7 @@ AuthDB.prototype.lookup = async function lookup() {
* @returns {Promise} * @returns {Promise}
*/ */
AuthDB.prototype.populate = async function populate(addr, key) { async populate(addr, key) {
assert(addr.type === IP.types.DNS, 'Resolved host passed.'); assert(addr.type === IP.types.DNS, 'Resolved host passed.');
this.logger.info('Resolving authorized hosts from: %s.', addr.host); this.logger.info('Resolving authorized hosts from: %s.', addr.host);
@ -649,15 +654,15 @@ AuthDB.prototype.populate = async function populate(addr, key) {
this.known.set(host, key); this.known.set(host, key);
} }
}; }
/** /**
* Parse known peers. * Parse known peers.
* @param {String} text * @param {String} text
* @returns {Object} * @returns {Object}
*/ */
AuthDB.prototype.readKnown = async function readKnown() { async readKnown() {
if (fs.unsupported) if (fs.unsupported)
return; return;
@ -676,15 +681,15 @@ AuthDB.prototype.readKnown = async function readKnown() {
} }
this.parseKnown(text); this.parseKnown(text);
}; }
/** /**
* Parse known peers. * Parse known peers.
* @param {String} text * @param {String} text
* @returns {Object} * @returns {Object}
*/ */
AuthDB.prototype.parseKnown = function parseKnown(text) { parseKnown(text) {
assert(typeof text === 'string'); assert(typeof text === 'string');
if (text.charCodeAt(0) === 0xfeff) if (text.charCodeAt(0) === 0xfeff)
@ -735,15 +740,15 @@ AuthDB.prototype.parseKnown = function parseKnown(text) {
this.addKnown(addr, key); this.addKnown(addr, key);
} }
}; }
/** /**
* Parse known peers. * Parse known peers.
* @param {String} text * @param {String} text
* @returns {Object} * @returns {Object}
*/ */
AuthDB.prototype.readAuth = async function readAuth() { async readAuth() {
if (fs.unsupported) if (fs.unsupported)
return; return;
@ -762,15 +767,15 @@ AuthDB.prototype.readAuth = async function readAuth() {
} }
this.parseAuth(text); this.parseAuth(text);
}; }
/** /**
* Parse authorized peers. * Parse authorized peers.
* @param {String} text * @param {String} text
* @returns {Buffer[]} keys * @returns {Buffer[]} keys
*/ */
AuthDB.prototype.parseAuth = function parseAuth(text) { parseAuth(text) {
assert(typeof text === 'string'); assert(typeof text === 'string');
if (text.charCodeAt(0) === 0xfeff) if (text.charCodeAt(0) === 0xfeff)
@ -799,7 +804,8 @@ AuthDB.prototype.parseAuth = function parseAuth(text) {
this.addAuthorized(key); this.addAuthorized(key);
} }
}; }
}
/* /*
* Expose * Expose

View File

@ -39,10 +39,9 @@ const INFO_SID = Buffer.from('BitcoinSessionID', 'ascii');
const HIGH_WATERMARK = 1024 * (1 << 20); const HIGH_WATERMARK = 1024 * (1 << 20);
/** /**
* BIP151 Stream
* Represents a BIP151 input or output stream. * Represents a BIP151 input or output stream.
* @alias module:net.BIP151Stream * @alias module:net.BIP151Stream
* @constructor
* @param {Number} cipher
* @property {Buffer} publicKey * @property {Buffer} publicKey
* @property {Buffer} privateKey * @property {Buffer} privateKey
* @property {Number} cipher * @property {Number} cipher
@ -57,10 +56,14 @@ const HIGH_WATERMARK = 1024 * (1 << 20);
* @property {Number} lastKey * @property {Number} lastKey
*/ */
function BIP151Stream(cipher) { class BIP151Stream {
if (!(this instanceof BIP151Stream)) /**
return new BIP151Stream(cipher); * Create a BIP151 input or output stream.
* @constructor
* @param {Number} cipher
*/
constructor(cipher) {
this.cipher = BIP151.ciphers.CHACHAPOLY; this.cipher = BIP151.ciphers.CHACHAPOLY;
this.privateKey = secp256k1.generatePrivateKey(); this.privateKey = secp256k1.generatePrivateKey();
this.publicKey = null; this.publicKey = null;
@ -82,15 +85,15 @@ function BIP151Stream(cipher) {
this.processed = 0; this.processed = 0;
this.lastRekey = 0; this.lastRekey = 0;
} }
/** /**
* Initialize the stream with peer's public key. * Initialize the stream with peer's public key.
* Computes ecdh secret and chacha keys. * Computes ecdh secret and chacha keys.
* @param {Buffer} publicKey * @param {Buffer} publicKey
*/ */
BIP151Stream.prototype.init = function init(publicKey) { init(publicKey) {
assert(Buffer.isBuffer(publicKey)); assert(Buffer.isBuffer(publicKey));
this.publicKey = publicKey; this.publicKey = publicKey;
@ -117,16 +120,16 @@ BIP151Stream.prototype.init = function init(publicKey) {
this.aead.init(this.k2, this.iv); this.aead.init(this.k2, this.iv);
this.lastRekey = util.now(); this.lastRekey = util.now();
}; }
/** /**
* Add buffer size to `processed`, * Add buffer size to `processed`,
* check whether we need to rekey. * check whether we need to rekey.
* @param {Buffer} packet * @param {Buffer} packet
* @returns {Boolean} * @returns {Boolean}
*/ */
BIP151Stream.prototype.shouldRekey = function shouldRekey(packet) { shouldRekey(packet) {
const now = util.now(); const now = util.now();
this.processed += packet.length; this.processed += packet.length;
@ -139,14 +142,14 @@ BIP151Stream.prototype.shouldRekey = function shouldRekey(packet) {
} }
return false; return false;
}; }
/** /**
* Generate new chacha keys with `key = HASH256(sid | key)`. * Generate new chacha keys with `key = HASH256(sid | key)`.
* This will reinitialize the state of both ciphers. * This will reinitialize the state of both ciphers.
*/ */
BIP151Stream.prototype.rekey = function rekey(k1, k2) { rekey(k1, k2) {
assert(this.sid, 'Cannot rekey before initialization.'); assert(this.sid, 'Cannot rekey before initialization.');
if (!k1) { if (!k1) {
@ -164,16 +167,16 @@ BIP151Stream.prototype.rekey = function rekey(k1, k2) {
// aside from the sequence number. // aside from the sequence number.
this.chacha.init(this.k1, this.iv); this.chacha.init(this.k1, this.iv);
this.aead.init(this.k2, this.iv); this.aead.init(this.k2, this.iv);
}; }
/** /**
* Increment packet sequence number and update IVs * Increment packet sequence number and update IVs
* (note, sequence number overflows after 2^64-1). * (note, sequence number overflows after 2^64-1).
* The IV will be updated without reinitializing * The IV will be updated without reinitializing
* cipher state. * cipher state.
*/ */
BIP151Stream.prototype.sequence = function sequence() { sequence() {
// Wrap sequence number a la openssh. // Wrap sequence number a la openssh.
if (++this.seq === 0x100000000) if (++this.seq === 0x100000000)
this.seq = 0; this.seq = 0;
@ -184,105 +187,106 @@ BIP151Stream.prototype.sequence = function sequence() {
// unaltered aside from the iv. // unaltered aside from the iv.
this.chacha.init(null, this.iv); this.chacha.init(null, this.iv);
this.aead.init(null, this.iv); this.aead.init(null, this.iv);
}; }
/** /**
* Render the IV necessary for cipher streams. * Render the IV necessary for cipher streams.
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151Stream.prototype.update = function update() { update() {
this.iv.writeUInt32LE(this.seq, 0, true); this.iv.writeUInt32LE(this.seq, 0, true);
return this.iv; return this.iv;
}; }
/** /**
* Get public key tied to private key * Get public key tied to private key
* (not the same as BIP151Stream#publicKey). * (not the same as BIP151Stream#publicKey).
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151Stream.prototype.getPublicKey = function getPublicKey() { getPublicKey() {
return secp256k1.publicKeyCreate(this.privateKey, true); return secp256k1.publicKeyCreate(this.privateKey, true);
}; }
/** /**
* Encrypt a payload size with k1. * Encrypt a payload size with k1.
* @param {Buffer} data * @param {Buffer} data
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151Stream.prototype.encryptSize = function encryptSize(data) { encryptSize(data) {
return this.chacha.encrypt(data.slice(0, 4)); return this.chacha.encrypt(data.slice(0, 4));
}; }
/** /**
* Decrypt payload size with k1. * Decrypt payload size with k1.
* @param {Buffer} data * @param {Buffer} data
* @returns {Number} * @returns {Number}
*/ */
BIP151Stream.prototype.decryptSize = function decryptSize(data) { decryptSize(data) {
this.chacha.encrypt(data); this.chacha.encrypt(data);
return data.readUInt32LE(0, true); return data.readUInt32LE(0, true);
}; }
/** /**
* Encrypt payload with AEAD (update cipher and mac). * Encrypt payload with AEAD (update cipher and mac).
* @param {Buffer} data * @param {Buffer} data
* @returns {Buffer} data * @returns {Buffer} data
*/ */
BIP151Stream.prototype.encrypt = function encrypt(data) { encrypt(data) {
return this.aead.encrypt(data); return this.aead.encrypt(data);
}; }
/** /**
* Decrypt payload with AEAD (update cipher only). * Decrypt payload with AEAD (update cipher only).
* @param {Buffer} data * @param {Buffer} data
* @returns {Buffer} data * @returns {Buffer} data
*/ */
BIP151Stream.prototype.decrypt = function decrypt(data) { decrypt(data) {
return this.aead.chacha20.encrypt(data); return this.aead.chacha20.encrypt(data);
}; }
/** /**
* Authenticate payload with AEAD (update mac only). * Authenticate payload with AEAD (update mac only).
* @param {Buffer} data * @param {Buffer} data
* @returns {Buffer} data * @returns {Buffer} data
*/ */
BIP151Stream.prototype.auth = function auth(data) { auth(data) {
return this.aead.auth(data); return this.aead.auth(data);
}; }
/** /**
* Finalize AEAD and compute MAC. * Finalize AEAD and compute MAC.
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151Stream.prototype.final = function final() { final() {
this.tag = this.aead.final(); this.tag = this.aead.final();
return this.tag; return this.tag;
}; }
/** /**
* Verify tag against mac in constant time. * Verify tag against mac in constant time.
* @param {Buffer} tag * @param {Buffer} tag
* @returns {Boolean} * @returns {Boolean}
*/ */
BIP151Stream.prototype.verify = function verify(tag) { verify(tag) {
return Poly1305.verify(this.tag, tag); return Poly1305.verify(this.tag, tag);
}; }
}
/** /**
* BIP151
* Represents a BIP151 input and output stream. * Represents a BIP151 input and output stream.
* Holds state for peer communication. * Holds state for peer communication.
* @alias module:net.BIP151 * @alias module:net.BIP151
* @constructor * @extends EventEmitter
* @param {Number} cipher
* @property {BIP151Stream} input * @property {BIP151Stream} input
* @property {BIP151Stream} output * @property {BIP151Stream} output
* @property {Boolean} initReceived * @property {Boolean} initReceived
@ -295,11 +299,15 @@ BIP151Stream.prototype.verify = function verify(tag) {
* @property {Boolean} handshake * @property {Boolean} handshake
*/ */
function BIP151(cipher) { class BIP151 extends EventEmitter {
if (!(this instanceof BIP151)) /**
return new BIP151(cipher); * Create a BIP151 input and output stream.
* @constructor
* @param {Number} cipher
*/
EventEmitter.call(this); constructor(cipher) {
super();
this.input = new BIP151Stream(cipher); this.input = new BIP151Stream(cipher);
this.output = new BIP151Stream(cipher); this.output = new BIP151Stream(cipher);
@ -321,68 +329,49 @@ function BIP151(cipher) {
this.onShake = null; this.onShake = null;
this.bip150 = null; this.bip150 = null;
} }
Object.setPrototypeOf(BIP151.prototype, EventEmitter.prototype); /**
/**
* Cipher list.
* @enum {Number}
*/
BIP151.ciphers = {
CHACHAPOLY: 0
};
/**
* Max message size.
* @const {Number}
* @default
*/
BIP151.MAX_MESSAGE = 12 * 1000 * 1000;
/**
* Emit an error. * Emit an error.
* @param {...String} msg * @param {...String} msg
*/ */
BIP151.prototype.error = function error() { error() {
const msg = format.apply(null, arguments); const msg = format.apply(null, arguments);
this.emit('error', new Error(msg)); this.emit('error', new Error(msg));
}; }
/** /**
* Test whether handshake has completed. * Test whether handshake has completed.
* @returns {Boolean} * @returns {Boolean}
*/ */
BIP151.prototype.isReady = function isReady() { isReady() {
return this.initSent return this.initSent
&& this.ackReceived && this.ackReceived
&& this.initReceived && this.initReceived
&& this.ackSent; && this.ackSent;
}; }
/** /**
* Render an `encinit` packet. Contains the * Render an `encinit` packet. Contains the
* input public key and cipher number. * input public key and cipher number.
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151.prototype.toEncinit = function toEncinit() { toEncinit() {
assert(!this.initSent, 'Cannot init twice.'); assert(!this.initSent, 'Cannot init twice.');
this.initSent = true; this.initSent = true;
return new EncinitPacket(this.input.getPublicKey(), this.input.cipher); return new EncinitPacket(this.input.getPublicKey(), this.input.cipher);
}; }
/** /**
* Render `encack` packet. Contains the * Render `encack` packet. Contains the
* output stream public key. * output stream public key.
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151.prototype.toEncack = function toEncack() { toEncack() {
assert(this.output.sid, 'Cannot ack before init.'); assert(this.output.sid, 'Cannot ack before init.');
assert(!this.ackSent, 'Cannot ack twice.'); assert(!this.ackSent, 'Cannot ack twice.');
this.ackSent = true; this.ackSent = true;
@ -394,39 +383,39 @@ BIP151.prototype.toEncack = function toEncack() {
} }
return new EncackPacket(this.output.getPublicKey()); return new EncackPacket(this.output.getPublicKey());
}; }
/** /**
* Render `encack` packet with an all * Render `encack` packet with an all
* zero public key, notifying of a rekey * zero public key, notifying of a rekey
* for the output stream. * for the output stream.
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151.prototype.toRekey = function toRekey() { toRekey() {
assert(this.handshake, 'Cannot rekey before handshake.'); assert(this.handshake, 'Cannot rekey before handshake.');
return new EncackPacket(encoding.ZERO_KEY); return new EncackPacket(encoding.ZERO_KEY);
}; }
/** /**
* Handle `encinit` from remote peer. * Handle `encinit` from remote peer.
* @param {Buffer} * @param {Buffer}
*/ */
BIP151.prototype.encinit = function encinit(publicKey, cipher) { encinit(publicKey, cipher) {
assert(cipher === this.output.cipher, 'Cipher mismatch.'); assert(cipher === this.output.cipher, 'Cipher mismatch.');
assert(!this.initReceived, 'Already initialized.'); assert(!this.initReceived, 'Already initialized.');
assert(!this.completed, 'No encinit after timeout.'); assert(!this.completed, 'No encinit after timeout.');
this.initReceived = true; this.initReceived = true;
this.output.init(publicKey); this.output.init(publicKey);
}; }
/** /**
* Handle `encack` from remote peer. * Handle `encack` from remote peer.
* @param {Buffer} data * @param {Buffer} data
*/ */
BIP151.prototype.encack = function encack(publicKey) { encack(publicKey) {
assert(this.initSent, 'Unsolicited ACK.'); assert(this.initSent, 'Unsolicited ACK.');
if (publicKey.equals(encoding.ZERO_KEY)) { if (publicKey.equals(encoding.ZERO_KEY)) {
@ -452,14 +441,14 @@ BIP151.prototype.encack = function encack(publicKey) {
this.handshake = true; this.handshake = true;
this.emit('handshake'); this.emit('handshake');
} }
}; }
/** /**
* Cleanup handshake job. * Cleanup handshake job.
* @returns {Job} * @returns {Job}
*/ */
BIP151.prototype.cleanup = function cleanup() { cleanup() {
const job = this.job; const job = this.job;
assert(!this.completed, 'Already completed.'); assert(!this.completed, 'Already completed.');
@ -479,41 +468,41 @@ BIP151.prototype.cleanup = function cleanup() {
} }
return job; return job;
}; }
/** /**
* Complete the timeout for handshake. * Complete the timeout for handshake.
* @param {Object} result * @param {Object} result
*/ */
BIP151.prototype.resolve = function resolve(result) { resolve(result) {
const job = this.cleanup(); const job = this.cleanup();
job.resolve(result); job.resolve(result);
}; }
/** /**
* Complete the timeout for handshake with error. * Complete the timeout for handshake with error.
* @param {Error} err * @param {Error} err
*/ */
BIP151.prototype.reject = function reject(err) { reject(err) {
const job = this.cleanup(); const job = this.cleanup();
job.reject(err); job.reject(err);
}; }
/** /**
* Set a timeout and wait for handshake to complete. * Set a timeout and wait for handshake to complete.
* @param {Number} timeout - Timeout in ms. * @param {Number} timeout - Timeout in ms.
* @returns {Promise} * @returns {Promise}
*/ */
BIP151.prototype.wait = function wait(timeout) { wait(timeout) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._wait(timeout, resolve, reject); this._wait(timeout, resolve, reject);
}); });
}; }
/** /**
* Set a timeout and wait for handshake to complete. * Set a timeout and wait for handshake to complete.
* @private * @private
* @param {Number} timeout * @param {Number} timeout
@ -521,7 +510,7 @@ BIP151.prototype.wait = function wait(timeout) {
* @param {Function} reject * @param {Function} reject
*/ */
BIP151.prototype._wait = function _wait(timeout, resolve, reject) { _wait(timeout, resolve, reject) {
assert(!this.handshake, 'Cannot wait for init after handshake.'); assert(!this.handshake, 'Cannot wait for init after handshake.');
this.job = { resolve, reject }; this.job = { resolve, reject };
@ -532,26 +521,26 @@ BIP151.prototype._wait = function _wait(timeout, resolve, reject) {
this.onShake = this.resolve.bind(this); this.onShake = this.resolve.bind(this);
this.once('handshake', this.onShake); this.once('handshake', this.onShake);
}; }
/** /**
* Destroy BIP151 state and streams. * Destroy BIP151 state and streams.
*/ */
BIP151.prototype.destroy = function destroy() { destroy() {
if (!this.job) if (!this.job)
return; return;
this.reject(new Error('BIP151 stream was destroyed.')); this.reject(new Error('BIP151 stream was destroyed.'));
}; }
/** /**
* Add buffer size to `processed`, * Add buffer size to `processed`,
* check whether we need to rekey. * check whether we need to rekey.
* @param {Buffer} packet * @param {Buffer} packet
*/ */
BIP151.prototype.maybeRekey = function maybeRekey(packet) { maybeRekey(packet) {
if (!this.output.shouldRekey(packet)) if (!this.output.shouldRekey(packet))
return; return;
@ -563,16 +552,16 @@ BIP151.prototype.maybeRekey = function maybeRekey(packet) {
} }
this.output.rekey(); this.output.rekey();
}; }
/** /**
* Calculate packet size. * Calculate packet size.
* @param {String} cmd * @param {String} cmd
* @param {Buffer} body * @param {Buffer} body
* @returns {Number} * @returns {Number}
*/ */
BIP151.prototype.packetSize = function packetSize(cmd, body) { packetSize(cmd, body) {
let size = 0; let size = 0;
size += 4; size += 4;
size += encoding.sizeVarString(cmd, 'ascii'); size += encoding.sizeVarString(cmd, 'ascii');
@ -580,16 +569,16 @@ BIP151.prototype.packetSize = function packetSize(cmd, body) {
size += body.length; size += body.length;
size += 16; size += 16;
return size; return size;
}; }
/** /**
* Frame plaintext payload for the output stream. * Frame plaintext payload for the output stream.
* @param {String} cmd * @param {String} cmd
* @param {Buffer} body * @param {Buffer} body
* @returns {Buffer} Ciphertext payload * @returns {Buffer} Ciphertext payload
*/ */
BIP151.prototype.packet = function packet(cmd, body) { packet(cmd, body) {
const size = this.packetSize(cmd, body); const size = this.packetSize(cmd, body);
const bw = bio.write(size); const bw = bio.write(size);
const payloadSize = size - 20; const payloadSize = size - 20;
@ -611,16 +600,16 @@ BIP151.prototype.packet = function packet(cmd, body) {
this.output.sequence(); this.output.sequence();
return msg; return msg;
}; }
/** /**
* Feed ciphertext payload chunk * Feed ciphertext payload chunk
* to the input stream. Potentially * to the input stream. Potentially
* emits a `packet` event. * emits a `packet` event.
* @param {Buffer} data * @param {Buffer} data
*/ */
BIP151.prototype.feed = function feed(data) { feed(data) {
this.total += data.length; this.total += data.length;
this.pending.push(data); this.pending.push(data);
@ -628,16 +617,16 @@ BIP151.prototype.feed = function feed(data) {
const chunk = this.read(this.waiting); const chunk = this.read(this.waiting);
this.parse(chunk); this.parse(chunk);
} }
}; }
/** /**
* Read and consume a number of bytes * Read and consume a number of bytes
* from the buffered stream. * from the buffered stream.
* @param {Number} size * @param {Number} size
* @returns {Buffer} * @returns {Buffer}
*/ */
BIP151.prototype.read = function read(size) { read(size) {
assert(this.total >= size, 'Reading too much.'); assert(this.total >= size, 'Reading too much.');
if (size === 0) if (size === 0)
@ -676,15 +665,15 @@ BIP151.prototype.read = function read(size) {
this.total -= chunk.length; this.total -= chunk.length;
return chunk; return chunk;
}; }
/** /**
* Parse a ciphertext payload chunk. * Parse a ciphertext payload chunk.
* Potentially emits a `packet` event. * Potentially emits a `packet` event.
* @param {Buffer} data * @param {Buffer} data
*/ */
BIP151.prototype.parse = function parse(data) { parse(data) {
if (!this.hasSize) { if (!this.hasSize) {
const size = this.input.decryptSize(data); const size = this.input.decryptSize(data);
@ -744,8 +733,26 @@ BIP151.prototype.parse = function parse(data) {
this.emit('packet', cmd, body); this.emit('packet', cmd, body);
} }
}
}
/**
* Cipher list.
* @enum {Number}
*/
BIP151.ciphers = {
CHACHAPOLY: 0
}; };
/**
* Max message size.
* @const {Number}
* @default
*/
BIP151.MAX_MESSAGE = 12 * 1000 * 1000;
/* /*
* Expose * Expose
*/ */

View File

@ -12,20 +12,22 @@ const Network = require('../protocol/network');
const hash256 = require('bcrypto/lib/hash256'); const hash256 = require('bcrypto/lib/hash256');
/** /**
* Protocol packet framer * Protocol Message Framer
* @alias module:net.Framer * @alias module:net.Framer
*/
class Framer {
/**
* Create a framer.
* @constructor * @constructor
* @param {Network} network * @param {Network} network
*/ */
function Framer(network) { constructor(network) {
if (!(this instanceof Framer))
return new Framer(network);
this.network = Network.get(network); this.network = Network.get(network);
} }
/** /**
* Frame a payload with a header. * Frame a payload with a header.
* @param {String} cmd - Packet type. * @param {String} cmd - Packet type.
* @param {Buffer} payload * @param {Buffer} payload
@ -33,7 +35,7 @@ function Framer(network) {
* @returns {Buffer} Payload with header prepended. * @returns {Buffer} Payload with header prepended.
*/ */
Framer.prototype.packet = function packet(cmd, payload, checksum) { packet(cmd, payload, checksum) {
assert(payload, 'No payload.'); assert(payload, 'No payload.');
assert(cmd.length < 12); assert(cmd.length < 12);
assert(payload.length <= 0xffffffff); assert(payload.length <= 0xffffffff);
@ -61,7 +63,8 @@ Framer.prototype.packet = function packet(cmd, payload, checksum) {
payload.copy(msg, 24); payload.copy(msg, 24);
return msg; return msg;
}; }
}
/* /*
* Expose * Expose

File diff suppressed because it is too large Load Diff

View File

@ -18,19 +18,22 @@ const common = require('./common');
const packets = require('./packets'); const packets = require('./packets');
/** /**
* Protocol packet parser * Protocol Message Parser
* @alias module:net.Parser * @alias module:net.Parser
* @constructor * @extends EventEmitter
* @param {Network} network
* @emits Parser#error * @emits Parser#error
* @emits Parser#packet * @emits Parser#packet
*/ */
function Parser(network) { class Parser extends EventEmitter {
if (!(this instanceof Parser)) /**
return new Parser(network); * Create a parser.
* @constructor
* @param {Network} network
*/
EventEmitter.call(this); constructor(network) {
super();
this.network = Network.get(network); this.network = Network.get(network);
@ -38,27 +41,25 @@ function Parser(network) {
this.total = 0; this.total = 0;
this.waiting = 24; this.waiting = 24;
this.header = null; this.header = null;
} }
Object.setPrototypeOf(Parser.prototype, EventEmitter.prototype); /**
/**
* Emit an error. * Emit an error.
* @private * @private
* @param {...String} msg * @param {...String} msg
*/ */
Parser.prototype.error = function error() { error() {
const msg = format.apply(null, arguments); const msg = format.apply(null, arguments);
this.emit('error', new Error(msg)); this.emit('error', new Error(msg));
}; }
/** /**
* Feed data to the parser. * Feed data to the parser.
* @param {Buffer} data * @param {Buffer} data
*/ */
Parser.prototype.feed = function feed(data) { feed(data) {
this.total += data.length; this.total += data.length;
this.pending.push(data); this.pending.push(data);
@ -80,14 +81,14 @@ Parser.prototype.feed = function feed(data) {
this.total -= chunk.length; this.total -= chunk.length;
this.parse(chunk); this.parse(chunk);
} }
}; }
/** /**
* Parse a fully-buffered chunk. * Parse a fully-buffered chunk.
* @param {Buffer} chunk * @param {Buffer} chunk
*/ */
Parser.prototype.parse = function parse(data) { parse(data) {
assert(data.length <= common.MAX_MESSAGE); assert(data.length <= common.MAX_MESSAGE);
if (!this.header) { if (!this.header) {
@ -119,15 +120,15 @@ Parser.prototype.parse = function parse(data) {
this.header = null; this.header = null;
this.emit('packet', payload); this.emit('packet', payload);
}; }
/** /**
* Parse buffered packet header. * Parse buffered packet header.
* @param {Buffer} data - Header. * @param {Buffer} data - Header.
* @returns {Header} * @returns {Header}
*/ */
Parser.prototype.parseHeader = function parseHeader(data) { parseHeader(data) {
const magic = data.readUInt32LE(0, true); const magic = data.readUInt32LE(0, true);
if (magic !== this.network.magic) { if (magic !== this.network.magic) {
@ -159,29 +160,36 @@ Parser.prototype.parseHeader = function parseHeader(data) {
const checksum = data.readUInt32LE(20, true); const checksum = data.readUInt32LE(20, true);
return new Header(cmd, size, checksum); return new Header(cmd, size, checksum);
}; }
/** /**
* Parse a payload. * Parse a payload.
* @param {String} cmd - Packet type. * @param {String} cmd - Packet type.
* @param {Buffer} data - Payload. * @param {Buffer} data - Payload.
* @returns {Object} * @returns {Object}
*/ */
Parser.prototype.parsePayload = function parsePayload(cmd, data) { parsePayload(cmd, data) {
return packets.fromRaw(cmd, data); return packets.fromRaw(cmd, data);
}; }
}
/** /**
* Packet Header * Packet Header
* @constructor
* @ignore * @ignore
*/ */
function Header(cmd, size, checksum) { class Header {
/**
* Create a header.
* @constructor
*/
constructor(cmd, size, checksum) {
this.cmd = cmd; this.cmd = cmd;
this.size = size; this.size = size;
this.checksum = checksum; this.checksum = checksum;
}
} }
/* /*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff