net: classify.
This commit is contained in:
parent
8be995bd78
commit
4ebfb5d9ff
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
779
lib/net/peer.js
779
lib/net/peer.js
File diff suppressed because it is too large
Load Diff
1061
lib/net/pool.js
1061
lib/net/pool.js
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user