diff --git a/etc/sample.conf b/etc/sample.conf index f8095883..2ab32f5f 100644 --- a/etc/sample.conf +++ b/etc/sample.conf @@ -36,8 +36,10 @@ index-address: false headers: true compact: true bip151: true +# bip150: false +# identity-key: 74b4147957813b62cc8987f2b711ddb31f8cb46dcbf71502033da66053c8780a # proxy-server: localhost -# preferred-seed: +# preferred-seed: seed.bitcoin.sipa.be # ignore-discovery: false # port: 8333 listen: true @@ -54,6 +56,6 @@ listen: true # ssl-key: /path/to/key # http-port: 8332 # http-host: 0.0.0.0 -# api-key: 74b4147957813b62cc8987f2b711ddb31f8cb46dcbf71502033da66053c8780a +# api-key: foobar # wallet-auth: true # no-auth: false diff --git a/lib/bcoin/bip150.js b/lib/bcoin/bip150.js index f4a248b4..26dbef49 100644 --- a/lib/bcoin/bip150.js +++ b/lib/bcoin/bip150.js @@ -290,15 +290,37 @@ function AuthDB() { AuthDB.prototype.addKnown = function addKnown(host, key) { assert(typeof host === 'string'); - assert(Buffer.isBuffer(key) && key.length === 33); + assert(Buffer.isBuffer(key) && key.length === 33, + 'Invalid public key for known peer.'); this.known[host] = key; }; +AuthDB.prototype.setKnown = function setKnown(map) { + var keys = Object.keys(map); + var i, host, key; + + for (i = 0; i < keys.length; i++) { + host = keys[i]; + key = map[host]; + this.addKnown(host, key); + } +}; + AuthDB.prototype.addAuthorized = function addAuthorized(key) { - assert(Buffer.isBuffer(key) && key.length === 33); + assert(Buffer.isBuffer(key) && key.length === 33, + 'Invalid public key for authorized peer.'); this.auth.push(key); }; +AuthDB.prototype.setAuthorized = function setAuthorized(auth) { + var i, key; + + for (i = 0; i < auth.length; i++) { + key = auth[i]; + this.addAuthorized(key); + } +}; + AuthDB.prototype.getKnown = function getKnown(host) { return this.known[host]; }; diff --git a/lib/bcoin/config.js b/lib/bcoin/config.js index 486ffd2d..660b4ccf 100644 --- a/lib/bcoin/config.js +++ b/lib/bcoin/config.js @@ -46,9 +46,8 @@ config.alias = { config.parse = function parse(options) { var env = {}; var arg = {}; - var text = {}; var data = {}; - var prefix; + var text, prefix; if (!options) options = {}; @@ -70,11 +69,18 @@ config.parse = function parse(options) { prefix = config.getPrefix(data); text = config.parseFile(prefix + '/bcoin.conf'); } + + data = merge(text, data); + prefix = config.getPrefix(data); + + if (!data.knownPeers) + data.knownPeers = config.parseKnown(prefix + '/known-peers'); + + if (!text.authPeers) + data.authPeers = config.parseAuth(prefix + '/authorized-peers'); } - merge(text, data); - - return text; + return data; }; /** @@ -151,6 +157,7 @@ config.parseData = function parseData(data) { options.compact = bool(data.compact); options.bip151 = bool(data.bip151); options.bip150 = bool(data.bip150); + options.identityKey = key(data.identitykey); options.proxyServer = str(data.proxyserver); options.preferredSeed = str(data.preferredseed); options.maxPeers = num(data.maxpeers); @@ -188,6 +195,26 @@ config.parseFile = function parseFile(file) { return config.parseText(readFile(file)); }; +/** + * Parse known peers file. + * @param {String} file + * @returns {Object} + */ + +config.parseKnown = function parseKnown(file) { + return config.parseKnownText(readFile(file)); +}; + +/** + * Parse authorized peers file. + * @param {String} file + * @returns {Object} + */ + +config.parseAuth = function parseAuth(file) { + return config.parseAuthText(readFile(file)); +}; + /** * Parse config text. * @param {String} text @@ -359,6 +386,86 @@ config.parseEnv = function parseEnv(env) { return config.parseData(data); }; +/** + * Parse known peers. + * @param {String} text + * @returns {Object} + */ + +config.parseKnownText = function parseKnownText(text) { + var lines = text.split(/\n+/); + var map = {}; + var i, line, parts, hostname, host, ip, key; + + for (i = 0; i < lines.length; i++) { + line = lines[i].trim(); + + if (line.length === 0) + continue; + + if (/^\s*#/.test(line)) + continue; + + parts = line.split(/\s+/); + + if (parts.length < 2) + continue; + + hostname = parts[0].trim().split(','); + + if (hostname.length >= 2) { + host = hostname[0]; + ip = hostname[1]; + } else { + ip = hostname[0]; + } + + key = parts[1].trim(); + key = new Buffer(key, 'hex'); + + if (key.length !== 33) + throw new Error('Invalid key: ' + parts[1]); + + if (host) + map[host] = key; + + map[ip] = key; + } + + return map; +}; + +/** + * Parse authorized peers. + * @param {String} text + * @returns {Buffer[]} keys + */ + +config.parseAuthText = function parseAuthText(text) { + var lines = text.split(/\n+/); + var keys = []; + var i, line, key; + + for (i = 0; i < lines.length; i++) { + line = lines[i].trim(); + + if (line.length === 0) + continue; + + if (/^\s*#/.test(line)) + continue; + + key = new Buffer(line, 'hex'); + + if (key.length !== 33) + throw new Error('Invalid key: ' + line); + + keys.push(key); + } + + return keys; +}; + /* * Helpers */ @@ -369,6 +476,23 @@ function str(value) { return value; } +function key(value) { + var key; + + if (!value) + return null; + + if (typeof value !== 'string') + return null; + + key = new Buffer(value, 'hex'); + + if (key.length !== 32) + throw new Error('Invalid key: ' + value); + + return key; +} + function path(value) { if (!value) return null; @@ -447,6 +571,8 @@ function merge(a, b) { if (b[key] != null) a[key] = b[key]; } + + return a; } /* diff --git a/lib/bcoin/ec.js b/lib/bcoin/ec.js index f9310904..09ecc5be 100644 --- a/lib/bcoin/ec.js +++ b/lib/bcoin/ec.js @@ -357,6 +357,9 @@ ec.privateKeyVerify = function privateKeyVerify(key) { if (secp256k1) return secp256k1.privateKeyVerify(key); + if (key.length !== 32) + return false; + key = new bn(key); return key.cmpn(0) !== 0 && key.cmp(ec.elliptic.curve.n) < 0; diff --git a/lib/bcoin/fullnode.js b/lib/bcoin/fullnode.js index 0ea4d2b2..114154e0 100644 --- a/lib/bcoin/fullnode.js +++ b/lib/bcoin/fullnode.js @@ -101,8 +101,9 @@ function Fullnode(options) { compact: this.options.compact, bip151: this.options.bip151, bip150: this.options.bip150, - auth: this.options.auth, - identity: this.options.identity, + authPeers: this.options.authPeers, + knownPeers: this.options.knownPeers, + identityKey: this.options.identityKey, maxPeers: this.options.maxPeers, maxLeeches: this.options.maxLeeches, proxyServer: this.options.proxyServer, diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index fface8b5..fd0ab31a 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -148,7 +148,7 @@ function Peer(pool, options) { this.hostname, this.type !== Peer.types.LEECH, this.pool.auth, - this.pool.identity); + this.pool.identityKey); this.bip151.bip150 = this.bip150; } } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index dec01c7e..60f7082a 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -147,12 +147,23 @@ function Pool(options) { this.locker = new bcoin.locker(this); this.proxyServer = options.proxyServer; this.auth = null; - this.identity = null; + this.identityKey = null; if (this.options.bip150) { this.options.bip151 = true; - this.auth = options.auth || new bcoin.bip150.AuthDB(); - this.identity = options.identity || bcoin.ec.generatePrivateKey(); + this.auth = new bcoin.bip150.AuthDB(); + + if (options.authPeers) + this.auth.setAuthorized(options.authPeers); + + if (options.knownPeers) + this.auth.setKnown(options.knownPeers); + + this.identityKey = options.identityKey || bcoin.ec.generatePrivateKey(); + + assert(Buffer.isBuffer(this.identityKey), 'Identity key must be a buffer.'); + assert(bcoin.ec.privateKeyVerify(this.identityKey), + 'Invalid identity key.'); } this.syncing = false; @@ -341,6 +352,11 @@ Pool.prototype._open = function _open(callback) { self.logger.info('Pool loaded (maxpeers=%d).', self.maxPeers); + if (self.identityKey) { + self.logger.info('Identity public key: %s', + bcoin.ec.publicKeyCreate(self.identityKey, true).toString('hex')); + } + if (!self.options.listen) return callback(); diff --git a/lib/bcoin/spvnode.js b/lib/bcoin/spvnode.js index b1b09a14..f543b7da 100644 --- a/lib/bcoin/spvnode.js +++ b/lib/bcoin/spvnode.js @@ -58,6 +58,11 @@ function SPVNode(options) { witness: this.options.witness, proxyServer: this.options.proxyServer, preferredSeed: this.options.preferredSeed, + bip151: this.options.bip151, + bip150: this.options.bip150, + authPeers: this.options.authPeers, + knownPeers: this.options.knownPeers, + identityKey: this.options.identityKey, maxPeers: this.options.maxPeers, ignoreDiscovery: this.options.ignoreDiscovery, selfish: true,