Implemented basic wallet db and calls to bitcoind.
This commit is contained in:
parent
86186e6147
commit
e6d569620b
@ -138,21 +138,15 @@ function loadModule(req, service) {
|
|||||||
try {
|
try {
|
||||||
// first try in the built-in bitcore-node services directory
|
// first try in the built-in bitcore-node services directory
|
||||||
var serviceFile = path.resolve(__dirname, '../services/' + service.name);
|
var serviceFile = path.resolve(__dirname, '../services/' + service.name);
|
||||||
if (fs.existsSync(serviceFile + '.js')) {
|
service.module = req(serviceFile);
|
||||||
// if the file exists, we can require it, then if there is a problem, catch and display the error
|
|
||||||
service.module = req(serviceFile);
|
|
||||||
} else {
|
|
||||||
// check if the package.json specifies a specific file to use
|
|
||||||
var servicePackage = req(service.name + '/package.json');
|
|
||||||
var serviceModule = service.name;
|
|
||||||
if (servicePackage.bitcoreNode) {
|
|
||||||
serviceModule = service.name + '/' + servicePackage.bitcoreNode;
|
|
||||||
}
|
|
||||||
service.module = req(serviceModule);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log.error(e.stack);
|
log.error(e.stack);
|
||||||
process.exit(-1);
|
var servicePackage = req(service.name + '/package.json');
|
||||||
|
var serviceModule = service.name;
|
||||||
|
if (servicePackage.bitcoreNode) {
|
||||||
|
serviceModule = service.name + '/' + servicePackage.bitcoreNode;
|
||||||
|
}
|
||||||
|
service.module = req(serviceModule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
242
lib/services/wallet/index.js
Normal file
242
lib/services/wallet/index.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BaseService = require('../../service');
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
var index = require('../../');
|
||||||
|
var log = index.log;
|
||||||
|
var errors = index.errors;
|
||||||
|
var bitcore = require('bitcore-lib');
|
||||||
|
var Networks = bitcore.Networks;
|
||||||
|
var levelup = require('levelup');
|
||||||
|
var leveldown = require('leveldown');
|
||||||
|
var multer = require('multer');
|
||||||
|
var storage = multer.memoryStorage();
|
||||||
|
var upload = multer({ storage: storage });
|
||||||
|
var validators = require('./validators');
|
||||||
|
var utils = require('./utils');
|
||||||
|
/**
|
||||||
|
* The Address Service builds upon the Database Service and the Bitcoin Service to add additional
|
||||||
|
* functionality for getting information by base58check encoded addresses. This includes getting the
|
||||||
|
* balance for an address, the history for a collection of addresses, and unspent outputs for
|
||||||
|
* constructing transactions. This is typically the core functionality for building a wallet.
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Node} options.node - An instance of the node
|
||||||
|
* @param {String} options.name - An optional name of the service
|
||||||
|
*/
|
||||||
|
var WalletService = function(options) {
|
||||||
|
BaseService.call(this, options);
|
||||||
|
this._dbOptions = {
|
||||||
|
keyEncoding: 'string',
|
||||||
|
valueEncoding: 'json'
|
||||||
|
};
|
||||||
|
this._db = levelup(options.dbPath, this._dbOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
inherits(WalletService, BaseService);
|
||||||
|
|
||||||
|
WalletService.dependencies = [
|
||||||
|
'bitcoind',
|
||||||
|
'web'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the Node to get the available API methods for this service,
|
||||||
|
* that can be exposed over the JSON-RPC interface.
|
||||||
|
*/
|
||||||
|
WalletService.prototype.getAPIMethods = function() {
|
||||||
|
return [
|
||||||
|
//['getWalletInfo', this, this.getInfo, 0]
|
||||||
|
//['getWalletBalance', this, this.getWalletBalance, 2],
|
||||||
|
//['getBlockTimestampInfo', this, this.getBlockTimestampInfo, 2],
|
||||||
|
//['addWalletAddresses', this, this., 2],
|
||||||
|
//['addWalletAddress', this, this.addWalletAddress, 2],
|
||||||
|
//['addWallet', this, this.addWallet, 2],
|
||||||
|
//['getWalletUtxos', this, this.getWalletUtxos, 2],
|
||||||
|
//['getWalletRawTransactions', this, this.getWalletRawTransactions, 1]
|
||||||
|
//['getWalletTxids', this, this.getWalletTxids, 1]
|
||||||
|
//['getWalletTransactions', this, this.getWalletTransactions, 1]
|
||||||
|
];
|
||||||
|
};
|
||||||
|
WalletService.prototype.start = function(callback) {
|
||||||
|
setImmediate(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype.stop = function(callback) {
|
||||||
|
setImmediate(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the Bus to get the available events for this service.
|
||||||
|
*/
|
||||||
|
WalletService.prototype.getPublishEvents = function() {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype._endpointUTXOs = function() {
|
||||||
|
var self = this;
|
||||||
|
return function(req, res) {
|
||||||
|
var walletId = req.params.walletId;
|
||||||
|
//var tip = self.node.bitcoind.tip;
|
||||||
|
// TODO: get the height of the tip
|
||||||
|
//var height = tip;
|
||||||
|
var height = null;
|
||||||
|
self._getUtxos(walletId, height, function(err, utxos) {
|
||||||
|
if(err) {
|
||||||
|
return utils.sendError(err);
|
||||||
|
}
|
||||||
|
res.status(200).jsonp({
|
||||||
|
utxos: utxos,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype._endpointGetBalance= function() {
|
||||||
|
var self = this;
|
||||||
|
return function(req, res) {
|
||||||
|
var walletId = req.params.walletId;
|
||||||
|
//var tip = self.node.bitcoind.tip;
|
||||||
|
// TODO: get the height of the tip
|
||||||
|
//var height = tip;
|
||||||
|
var height = null;
|
||||||
|
self._getBalance(walletId, height, function(err, balance) {
|
||||||
|
if(err) {
|
||||||
|
return utils.sendError(err);
|
||||||
|
}
|
||||||
|
res.status(200).jsonp({
|
||||||
|
balance: balance,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype._endpointGetAddresses = function() {
|
||||||
|
var self = this;
|
||||||
|
return function(req, res) {
|
||||||
|
var walletId = req.params.walletId;
|
||||||
|
|
||||||
|
self._getAddresses(walletId, function(err, addresses) {
|
||||||
|
if(err) {
|
||||||
|
return utils.sendError(err);
|
||||||
|
}
|
||||||
|
res.status(200).jsonp({
|
||||||
|
addresses: addresses
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
WalletService.prototype._endpointPostAddresses = function() {
|
||||||
|
var self = this;
|
||||||
|
return function(req, res) {
|
||||||
|
var addresses = req.addresses;
|
||||||
|
var walletId = utils.getWalletId();
|
||||||
|
self._storeAddresses(walletId, addresses, function(err, hash) {
|
||||||
|
if(err) {
|
||||||
|
return utils.sendError(err, res);
|
||||||
|
}
|
||||||
|
res.status(201).jsonp({
|
||||||
|
walletId: walletId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype._getUtxos = function(walletId, height, callback) {
|
||||||
|
// TODO get the balance only to this height
|
||||||
|
var self = this;
|
||||||
|
self._getAddresses(walletId, function(err, addresses) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.node.services.bitcoind.getAddressUnspentOutputs(addresses, {queryMempool: false}, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
WalletService.prototype._getBalance = function(walletId, height, callback) {
|
||||||
|
// TODO get the balance only to this height
|
||||||
|
var self = this;
|
||||||
|
self._getAddresses(walletId, function(err, addresses) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.node.services.bitcoind.getAddressUnspentOutputs(addresses, {
|
||||||
|
queryMempool: false
|
||||||
|
}, function(err, utxos) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
var balance = 0;
|
||||||
|
utxos.forEach(function(utxo) {
|
||||||
|
balance += utxo.satoshis;
|
||||||
|
});
|
||||||
|
callback(null, balance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
WalletService.prototype._getAddresses = function(walletId, callback) {
|
||||||
|
this._db.get(walletId, callback);
|
||||||
|
};
|
||||||
|
WalletService.prototype._storeAddresses = function(walletId, addresses, callback) {
|
||||||
|
this._db.put(walletId, addresses, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype._endpointGetInfo = function() {
|
||||||
|
return function(req, res) {
|
||||||
|
res.jsonp({result: 'ok'});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
WalletService.prototype.setupRoutes = function(app, express) {
|
||||||
|
var s = this;
|
||||||
|
var v = validators;
|
||||||
|
app.get('/info',
|
||||||
|
s._endpointGetInfo()
|
||||||
|
);
|
||||||
|
//app.get('/wallets/:walletId/txids',
|
||||||
|
// v.checkWalletId,
|
||||||
|
// v.checkRangeParams,
|
||||||
|
// s._endpointTxids()
|
||||||
|
//);
|
||||||
|
//app.get('/wallets/:walletId/transactions',
|
||||||
|
// v.checkWalletId,
|
||||||
|
// v.checkRangeParams,
|
||||||
|
// s._endpointTransactions()
|
||||||
|
//);
|
||||||
|
//app.get('/wallets/:walletId/rawtransactions',
|
||||||
|
// v.checkWalletId,
|
||||||
|
// v.checkRangeParams,
|
||||||
|
// s._endpointRawTransactions()
|
||||||
|
//);
|
||||||
|
app.get('/wallets/:walletId/utxos',
|
||||||
|
s._endpointUTXOs()
|
||||||
|
);
|
||||||
|
app.get('/wallets/:walletId/balance',
|
||||||
|
s._endpointGetBalance()
|
||||||
|
);
|
||||||
|
app.get('/wallets/:walletId',
|
||||||
|
s._endpointGetAddresses()
|
||||||
|
);
|
||||||
|
app.post('/wallets/addresses',
|
||||||
|
upload.single('addresses'),
|
||||||
|
v.checkAddresses,
|
||||||
|
s._endpointPostAddresses()
|
||||||
|
);
|
||||||
|
//app.put('/wallets/:walletId',
|
||||||
|
// v.checkWalletId,
|
||||||
|
// s._endpointPutWallet()
|
||||||
|
//);
|
||||||
|
//app.get('/info/timestamps',
|
||||||
|
// s._endpointGetHeightsFromTimestamps()
|
||||||
|
//);
|
||||||
|
//app.get('/jobs/:jobId',
|
||||||
|
// s._endpointJobStatus()
|
||||||
|
//);
|
||||||
|
//app.use(s._endpointNotFound());
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletService.prototype.getRoutePrefix = function() {
|
||||||
|
return 'wallet';
|
||||||
|
};
|
||||||
|
module.exports = WalletService;
|
||||||
|
|
||||||
691
lib/services/wallet/utils.js
Normal file
691
lib/services/wallet/utils.js
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Writable = require('stream').Writable;
|
||||||
|
var assert = require('assert');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var fs = require('fs');
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
var path = require('path');
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
var BitcoinRPC = require('bitcoind-rpc');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var async = require('async');
|
||||||
|
var bitcore = require('bitcore-lib');
|
||||||
|
var mkdirp = require('mkdirp');
|
||||||
|
var ttyread = require('ttyread');
|
||||||
|
|
||||||
|
var exports = {};
|
||||||
|
|
||||||
|
exports.isInteger = function(value) {
|
||||||
|
return typeof value === 'number' &&
|
||||||
|
isFinite(value) &&
|
||||||
|
Math.floor(value) === value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a directory if it does not already exist.
|
||||||
|
*
|
||||||
|
* @param {String} directory - An absolute path to the directory
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
exports.setupDirectory = function(directory, callback) {
|
||||||
|
fs.access(directory, function(err) {
|
||||||
|
if (err && err.code === 'ENOENT') {
|
||||||
|
return mkdirp(directory, callback);
|
||||||
|
} else if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will split a range of numbers "a" to "b" by sections
|
||||||
|
* of the length "max".
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* > var range = utils.splitRange(1, 10, 3);
|
||||||
|
* > [[1, 3], [4, 6], [7, 9], [10, 10]]
|
||||||
|
*
|
||||||
|
* @param {Number} a - The start index (lesser)
|
||||||
|
* @param {Number} b - The end index (greater)
|
||||||
|
* @param {Number} max - The maximum section length
|
||||||
|
*/
|
||||||
|
exports.splitRange = function(a, b, max) {
|
||||||
|
assert(b > a, '"b" is expected to be greater than "a"');
|
||||||
|
var sections = [];
|
||||||
|
var delta = b - a;
|
||||||
|
var first = a;
|
||||||
|
var last = a;
|
||||||
|
|
||||||
|
var length = Math.floor(delta / max);
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
last = first + max - 1;
|
||||||
|
sections.push([first, last]);
|
||||||
|
first += max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last <= b) {
|
||||||
|
sections.push([first, b]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getFileStream: Checks for the file's existence and returns a readable stream or stdin
|
||||||
|
* @param {String} path - The path to the file
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
exports.getFileStream = function(filePath, callback) {
|
||||||
|
callback(null, fs.createReadStream(filePath));
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.readWalletDatFile = function(filePath, network, callback) {
|
||||||
|
assert(_.isString(network), 'Network expected to be a string.');
|
||||||
|
var datadir = path.dirname(filePath).replace(/(\/testnet3|\/regtest)$/, '');
|
||||||
|
var name = path.basename(filePath);
|
||||||
|
var options = ['-datadir=' + datadir, '-wallet=' + name];
|
||||||
|
if (network === 'testnet') {
|
||||||
|
options.push('-testnet');
|
||||||
|
} else if (network === 'regtest') {
|
||||||
|
options.push('-regtest');
|
||||||
|
}
|
||||||
|
// TODO use ../node_modules/.bin/wallet-utility
|
||||||
|
var exec = path.resolve(__dirname, '../node_modules/bitcore-node/bin/bitcoin-0.12.1/bin/wallet-utility');
|
||||||
|
var wallet = spawn(exec, options);
|
||||||
|
|
||||||
|
var result = '';
|
||||||
|
|
||||||
|
wallet.stdout.on('data', function(data) {
|
||||||
|
result += data.toString('utf8');
|
||||||
|
});
|
||||||
|
|
||||||
|
var error;
|
||||||
|
|
||||||
|
wallet.stderr.on('data', function(data) {
|
||||||
|
error = data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
wallet.on('close', function(code) {
|
||||||
|
if (code === 0) {
|
||||||
|
var addresses;
|
||||||
|
try {
|
||||||
|
addresses = JSON.parse(result);
|
||||||
|
addresses = addresses.map(function(entry) {
|
||||||
|
return entry.addr ? entry.addr : entry;
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null, addresses);
|
||||||
|
} else if (error) {
|
||||||
|
return callback(new Error(error));
|
||||||
|
} else {
|
||||||
|
var message = 'wallet-utility exited (' + code + '): ' + result;
|
||||||
|
return callback(new Error(message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.readWalletFile = function(filePath, network, callback) {
|
||||||
|
if (/\.dat$/.test(filePath)) {
|
||||||
|
exports.readWalletDatFile(filePath, network, callback);
|
||||||
|
} else {
|
||||||
|
exports.getFileStream(filePath, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will split an array into smaller arrays by size
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Number} size - The length of resulting smaller arrays
|
||||||
|
*/
|
||||||
|
exports.splitArray = function(array, size) {
|
||||||
|
var results = [];
|
||||||
|
while (array.length) {
|
||||||
|
results.push(array.splice(0, size));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to get the remote ip address from cloudflare headers.
|
||||||
|
*
|
||||||
|
* @param {Object} req - An express request object
|
||||||
|
*/
|
||||||
|
exports.getRemoteAddress = function(req) {
|
||||||
|
if (req.headers['cf-connecting-ip']) {
|
||||||
|
return req.headers['cf-connecting-ip'];
|
||||||
|
}
|
||||||
|
return req.socket.remoteAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A middleware to enable CORS
|
||||||
|
*
|
||||||
|
* @param {Object} req - An express request object
|
||||||
|
* @param {Object} res - An express response object
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
exports.enableCORS = function(req, res, next) {
|
||||||
|
res.header('access-control-allow-origin', '*');
|
||||||
|
res.header('access-control-allow-methods', 'GET, HEAD, PUT, POST, OPTIONS');
|
||||||
|
var allowed = [
|
||||||
|
'origin',
|
||||||
|
'x-requested-with',
|
||||||
|
'content-type',
|
||||||
|
'accept',
|
||||||
|
'content-length',
|
||||||
|
'cache-control',
|
||||||
|
'cf-connecting-ip'
|
||||||
|
];
|
||||||
|
res.header('access-control-allow-headers', allowed.join(', '));
|
||||||
|
|
||||||
|
var method = req.method && req.method.toUpperCase && req.method.toUpperCase();
|
||||||
|
|
||||||
|
if (method === 'OPTIONS') {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will send error to express response
|
||||||
|
*
|
||||||
|
* @param {Error} err - error object
|
||||||
|
* @param {Object} res - express response object
|
||||||
|
*/
|
||||||
|
exports.sendError = function(err, res) {
|
||||||
|
if (err.statusCode) {
|
||||||
|
res.status(err.statusCode).send(err.message);
|
||||||
|
} else {
|
||||||
|
console.error(err.stack);
|
||||||
|
res.status(503).send(err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create a writeable logger stream
|
||||||
|
*
|
||||||
|
* @param {Function} logger - Function to log information
|
||||||
|
* @returns {Stream}
|
||||||
|
*/
|
||||||
|
exports.createLogStream = function(logger) {
|
||||||
|
function Log(options) {
|
||||||
|
Writable.call(this, options);
|
||||||
|
}
|
||||||
|
inherits(Log, Writable);
|
||||||
|
|
||||||
|
Log.prototype._write = function (chunk, enc, callback) {
|
||||||
|
logger(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
var stream = new Log();
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getWalletId = function() {
|
||||||
|
return crypto.randomBytes(16).toString('hex');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getClients = function(clientsConfig) {
|
||||||
|
var clients = [];
|
||||||
|
for (var i = 0; i < clientsConfig.length; i++) {
|
||||||
|
var config = clientsConfig[i];
|
||||||
|
var remoteClient = new BitcoinRPC({
|
||||||
|
protocol: config.rpcprotocol || 'http',
|
||||||
|
host: config.rpchost || '127.0.0.1',
|
||||||
|
port: config.rpcport,
|
||||||
|
user: config.rpcuser,
|
||||||
|
pass: config.rpcpassword,
|
||||||
|
rejectUnauthorized: _.isUndefined(config.rpcstrict) ? true : config.rpcstrict
|
||||||
|
});
|
||||||
|
clients.push(remoteClient);
|
||||||
|
}
|
||||||
|
return clients;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.setClients = function(obj, clients) {
|
||||||
|
obj._clients = clients;
|
||||||
|
obj._clientsIndex = 0;
|
||||||
|
Object.defineProperty(obj, 'clients', {
|
||||||
|
get: function() {
|
||||||
|
var client = obj._clients[obj._clientsIndex];
|
||||||
|
obj._clientsIndex = (obj._clientsIndex + 1) % obj._clients.length;
|
||||||
|
return client;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.tryAllClients = function(obj, func, options, callback) {
|
||||||
|
if (_.isFunction(options)) {
|
||||||
|
callback = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
var clientIndex = obj._clientsIndex;
|
||||||
|
var retry = function(done) {
|
||||||
|
var client = obj._clients[clientIndex];
|
||||||
|
clientIndex = (clientIndex + 1) % obj._clients.length;
|
||||||
|
func(client, done);
|
||||||
|
};
|
||||||
|
async.retry({times: obj._clients.length, interval: options.interval || 1000}, retry, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.wrapRPCError = function(errObj) {
|
||||||
|
var err = new Error(errObj.message);
|
||||||
|
err.code = errObj.code;
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
var PUBKEYHASH = new Buffer('01', 'hex');
|
||||||
|
var SCRIPTHASH = new Buffer('02', 'hex');
|
||||||
|
|
||||||
|
exports.getAddressTypeString = function(bufferArg) {
|
||||||
|
var buffer = bufferArg;
|
||||||
|
if (!Buffer.isBuffer(bufferArg)) {
|
||||||
|
buffer = new Buffer(bufferArg, 'hex');
|
||||||
|
}
|
||||||
|
var type = buffer.slice(0, 1);
|
||||||
|
if (type.compare(PUBKEYHASH) === 0) {
|
||||||
|
return 'pubkeyhash';
|
||||||
|
} else if (type.compare(SCRIPTHASH) === 0) {
|
||||||
|
return 'scripthash';
|
||||||
|
} else {
|
||||||
|
throw new TypeError('Unknown address type');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getAddressTypeBuffer = function(address) {
|
||||||
|
var type;
|
||||||
|
if (address.type === 'pubkeyhash') {
|
||||||
|
type = PUBKEYHASH;
|
||||||
|
} else if (address.type === 'scripthash') {
|
||||||
|
type = SCRIPTHASH;
|
||||||
|
} else {
|
||||||
|
throw new TypeError('Unknown address type');
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.splitBuffer = function(buffer, size) {
|
||||||
|
var pos = 0;
|
||||||
|
var buffers = [];
|
||||||
|
while (pos < buffer.length) {
|
||||||
|
buffers.push(buffer.slice(pos, pos + size));
|
||||||
|
pos += size;
|
||||||
|
}
|
||||||
|
return buffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.exitWorker = function(worker, timeout, callback) {
|
||||||
|
assert(worker, '"worker" is expected to be defined');
|
||||||
|
var exited = false;
|
||||||
|
worker.once('exit', function(code) {
|
||||||
|
if (!exited) {
|
||||||
|
exited = true;
|
||||||
|
if (code !== 0) {
|
||||||
|
var error = new Error('Worker did not exit cleanly: ' + code);
|
||||||
|
error.code = code;
|
||||||
|
return callback(error);
|
||||||
|
} else {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
worker.kill('SIGINT');
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!exited) {
|
||||||
|
exited = true;
|
||||||
|
worker.kill('SIGKILL');
|
||||||
|
return callback(new Error('Worker exit timeout, force shutdown'));
|
||||||
|
}
|
||||||
|
}, timeout).unref();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.timestampToISOString = function(timestamp) {
|
||||||
|
return new Date(this.toIntIfNumberLike(timestamp) * 1000).toISOString();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.satoshisToBitcoin = function(satoshis) {
|
||||||
|
return satoshis / 100000000;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getPassphrase = function(callback) {
|
||||||
|
ttyread('Enter passphrase: ', {silent: true}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.acquirePassphrase = function(callback) {
|
||||||
|
var first;
|
||||||
|
var second;
|
||||||
|
async.doWhilst(function(next) {
|
||||||
|
ttyread('Enter passphrase: ', {silent: true}, function(err, result) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
first = result;
|
||||||
|
ttyread('Re-enter passphrase: ', {silent: true}, function(err, result) {
|
||||||
|
second = result;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, function() {
|
||||||
|
if (first !== second) {
|
||||||
|
console.log('Passphrases do not match, please re-enter.');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, first);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Important notes:
|
||||||
|
|
||||||
|
How the encryption/decryption schemes work.
|
||||||
|
1. The user's passphrase and salt are hashed using scrypt algorithm. You must store the salt.
|
||||||
|
On modern hardware this hashing function should take 1-2 seconds.
|
||||||
|
2. The resulting hash is 48 bytes. The first 32 bytes of this hash is the "key" and the last
|
||||||
|
16 bytes is the "iv" to decrypt the master key using AES256-cbc.
|
||||||
|
3. The plaintext "master key" is always 32 bytes and should be as random as possible.
|
||||||
|
You may pass in the plaintext master key to encryptSecret -or- /dev/random will be consulted.
|
||||||
|
4. The cipherText of the master key must be stored just like the salt. For added security, you
|
||||||
|
might store the cipherText of the master key separate from the cipherText.
|
||||||
|
For example, if an attacker discovers your passphrase and salt (the most likely scenario), they would
|
||||||
|
still require the cipherText of the master key in order to decrypt the cipherText of your private keys.
|
||||||
|
Storing your encrypted master key on another device would be a better choice than keeping your salt,
|
||||||
|
the cipherText of your master key and the cipherText of your private keys on the same computer system.
|
||||||
|
5. The plaintext master key is then used to encrypt/decrypt the bitcoin private keys. The private keys'
|
||||||
|
corresponding public key is used as the IV for the procedure.
|
||||||
|
|
||||||
|
|
||||||
|
Specific notes regarding how private keys are transferred from a traditional "wallet.dat" file used with
|
||||||
|
Bitcoin Core's Wallet:
|
||||||
|
|
||||||
|
1. Bitcoin Core's Wallet uses Berkeley DB version 4.8 to store secp256k1 elliptic curve private keys in WIF format.
|
||||||
|
2. The same Berkeley DB, internally called "main", also stores compressed public keys for the above private keys,
|
||||||
|
the master keys used to encrypt the above private keys and bitcoin transaction details relevant to those private keys
|
||||||
|
3. The underlying data structure for the Berkeley database is the B-Tree (balanced tree). This is a key-value data
|
||||||
|
structure, therefore the database is a key-value database.
|
||||||
|
Berkeley DB documentation also refers to this as "key-record"
|
||||||
|
This means that the data contained in this B-Tree is organized for high speed retrieval based on a key.
|
||||||
|
In other words the database is optimized for lookups.
|
||||||
|
4. The filename for this database file is called "wallet.dat" historically,
|
||||||
|
but you can rename it to whatever suits you
|
||||||
|
|
||||||
|
*/
|
||||||
|
//this function depends on the derivation method and its params that were originally used to hash the passphrase
|
||||||
|
//this could be SHA512, scrypt, etc.
|
||||||
|
exports.sha512KDF = function(passphrase, salt, derivationOptions, callback) {
|
||||||
|
if (!derivationOptions || derivationOptions.method !== 0 || !derivationOptions.rounds) {
|
||||||
|
return callback(new Error('SHA512 KDF method was called for, ' +
|
||||||
|
'yet the derivations options for it were not supplied.'));
|
||||||
|
}
|
||||||
|
var rounds = derivationOptions.rounds || 1;
|
||||||
|
//if salt was sent in as a string, we will have to assume the default encoding type
|
||||||
|
if (!Buffer.isBuffer(salt)) {
|
||||||
|
salt = new Buffer(salt, 'utf-8');
|
||||||
|
}
|
||||||
|
var derivation = Buffer.concat([new Buffer(''), new Buffer(passphrase), salt]);
|
||||||
|
for(var i = 0; i < rounds; i++) {
|
||||||
|
derivation = crypto.createHash('sha512').update(derivation).digest();
|
||||||
|
}
|
||||||
|
callback(null, derivation);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.hashPassphrase = function(opts) {
|
||||||
|
return exports.sha512KDF;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.decryptPrivateKey = function(opts, callback) {
|
||||||
|
exports.decryptSecret(opts, function(err, masterKey) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
opts.cipherText = opts.pkCipherText;
|
||||||
|
//decrypt the private here using the plainText master key as the "key"
|
||||||
|
//and the double sha256 compressed pub key as the "IV"
|
||||||
|
opts.key = masterKey;
|
||||||
|
opts.iv = bitcore.crypto.Hash.sha256sha256(new Buffer(opts.pubkey, 'hex'));
|
||||||
|
exports.decrypt(opts, function(err, privateKey) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, privateKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//call decryptSecret first
|
||||||
|
exports.encryptPrivateKeys = function(opts, callback) {
|
||||||
|
if (!opts.masterKey || !opts.keys) {
|
||||||
|
return callback(new Error('A decrypted master key, ' +
|
||||||
|
'compressed public keys and private keys are required for encryption.'));
|
||||||
|
}
|
||||||
|
if (!Buffer.isBuffer(opts.masterKey)) { //we'll have to assume the master key is utf-8 encoded
|
||||||
|
opts.masterKey = new Buffer(opts.masterKey);
|
||||||
|
}
|
||||||
|
assert(opts.masterKey.length === 32, 'Master Key must be 32 bytes in length, ' +
|
||||||
|
'if you have a hex string, please pass master key in as a buffer');
|
||||||
|
//if the master key is not 32 bytes, then take the sha256 hash
|
||||||
|
var ret = [];
|
||||||
|
async.mapLimit(opts.keys, 5, function(key, next) {
|
||||||
|
var iv = bitcore.crypto.Hash.sha256sha256(new Buffer(key.pubKey, 'hex')).slice(0, 16);
|
||||||
|
//do we want to encrypt WIF's or RAW private keys or does it matter?
|
||||||
|
exports.encrypt({
|
||||||
|
secret: key.privKey,
|
||||||
|
iv: iv,
|
||||||
|
key: opts.masterKey
|
||||||
|
}, next);
|
||||||
|
}, function(err, results) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
for(var i = 0; i < results.length; i++) {
|
||||||
|
ret.push({
|
||||||
|
cipherText: results[i],
|
||||||
|
checkHash: bitcore.crypto.Hash.sha256(new Buffer(opts.keys[i].pubKey + results[i])).toString('hex'),
|
||||||
|
type: 'encrypted private key',
|
||||||
|
pubKey: opts.keys[i].pubKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback(null, ret);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encrypt = function(opts, callback) {
|
||||||
|
if (!opts.key ||
|
||||||
|
!opts.iv ||
|
||||||
|
!opts.secret ||
|
||||||
|
opts.key.length !== 32 ||
|
||||||
|
opts.iv.length !== 16 ||
|
||||||
|
opts.secret.length < 1) {
|
||||||
|
return callback(new Error('Key, IV, and something to encrypt is required.'));
|
||||||
|
}
|
||||||
|
var cipher = crypto.createCipheriv('aes-256-cbc', opts.key, opts.iv);
|
||||||
|
var cipherText;
|
||||||
|
try {
|
||||||
|
cipherText = Buffer.concat([cipher.update(opts.secret), cipher.final()]).toString('hex');
|
||||||
|
} catch(e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
return callback(null, cipherText);
|
||||||
|
|
||||||
|
};
|
||||||
|
exports.encryptSecret = function(opts, callback) {
|
||||||
|
var hashFunc = exports.hashPassphrase(opts.derivationOptions);
|
||||||
|
hashFunc(opts.passphrase, opts.salt, opts.derivationOptions, function(err, hashedPassphrase) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
var secret = opts.secret || crypto.randomBytes(32);
|
||||||
|
assert(Buffer.isBuffer(secret), 'secret is expected to be a buffer');
|
||||||
|
secret = bitcore.crypto.Hash.sha256sha256(secret);
|
||||||
|
var firstHalf = hashedPassphrase.slice(0, 32); //AES256-cbc shared key
|
||||||
|
var secondHalf = hashedPassphrase.slice(32, 48); //AES256-cbc IV, for cbc mode, the IV will be 16 bytes
|
||||||
|
exports.encrypt({
|
||||||
|
secret: secret,
|
||||||
|
key: firstHalf,
|
||||||
|
iv: secondHalf
|
||||||
|
}, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.decryptSecret = function(opts, callback) {
|
||||||
|
var hashFunc = exports.hashPassphrase(opts.derivationOptions);
|
||||||
|
hashFunc(opts.passphrase, opts.salt, opts.derivationOptions, function(err, hashedPassphrase) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
opts.key = hashedPassphrase;
|
||||||
|
exports.decrypt(opts, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.decrypt = function(opts, callback) {
|
||||||
|
if (!Buffer.isBuffer(opts.key)) {
|
||||||
|
opts.key = new Buffer(opts.key, 'hex');
|
||||||
|
}
|
||||||
|
var secondHalf;
|
||||||
|
if (opts.iv) {
|
||||||
|
secondHalf = opts.iv.slice(0, 16);
|
||||||
|
} else {
|
||||||
|
secondHalf = opts.key.slice(32, 48); //AES256-cbc IV
|
||||||
|
}
|
||||||
|
var cipherText = new Buffer(opts.cipherText, 'hex');
|
||||||
|
var firstHalf = opts.key.slice(0, 32); //AES256-cbc shared key
|
||||||
|
var AESDecipher = crypto.createDecipheriv('aes-256-cbc', firstHalf, secondHalf);
|
||||||
|
var plainText;
|
||||||
|
try {
|
||||||
|
plainText = Buffer.concat([AESDecipher.update(cipherText), AESDecipher.final()]).toString('hex');
|
||||||
|
} catch(e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
callback(null, plainText);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.confirm = function(question, callback) {
|
||||||
|
ttyread(question + ' (y/N): ', function(err, answer) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err, false);
|
||||||
|
}
|
||||||
|
if (answer === 'y') {
|
||||||
|
return callback(null, true);
|
||||||
|
}
|
||||||
|
callback(null, false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encryptSecretWithPassphrase = function(secret, callback) {
|
||||||
|
exports.acquirePassphrase(function(err, passphrase) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
var salt = crypto.randomBytes(32).toString('hex');
|
||||||
|
exports.encryptSecret({
|
||||||
|
secret: secret,
|
||||||
|
passphrase: passphrase,
|
||||||
|
salt: salt
|
||||||
|
}, function(err, cipherText) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, cipherText, salt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.generateNonce = function() {
|
||||||
|
var nonce = new Buffer(new Array(12));
|
||||||
|
nonce.writeDoubleBE(Date.now());
|
||||||
|
nonce.writeUInt32BE(process.hrtime()[1], 8);
|
||||||
|
return nonce;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.generateHashForRequest = function(method, url, nonce) {
|
||||||
|
nonce = nonce || new Buffer(0);
|
||||||
|
assert(Buffer.isBuffer(nonce), 'nonce must a buffer');
|
||||||
|
var dataToSign = Buffer.concat([nonce, new Buffer(method), new Buffer(url)]);
|
||||||
|
return bitcore.crypto.Hash.sha256sha256(dataToSign);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getWalletIdFromName = function(walletName) {
|
||||||
|
if (!Buffer.isBuffer(walletName)) {
|
||||||
|
walletName = new Buffer(walletName, 'utf8');
|
||||||
|
}
|
||||||
|
return bitcore.crypto.Hash.sha256sha256(walletName).toString('hex');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.isRangeMoreThan = function(a, b) {
|
||||||
|
if (a && !b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!a && !b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!a && b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.height > b.height) {
|
||||||
|
return true;
|
||||||
|
} else if (a.height < b.height) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return a.index > b.index;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.toHexBuffer = function(a) {
|
||||||
|
if (!Buffer.isBuffer(a)) {
|
||||||
|
a = new Buffer(a, 'hex');
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.toIntIfNumberLike = function(a) {
|
||||||
|
if (!/[^\d]+/.test(a)) {
|
||||||
|
return parseInt(a);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.delimitedStringParse = function(delim, str) {
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
if (delim === null) {
|
||||||
|
return tryJSONparse(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = str.split(delim);
|
||||||
|
for(var i = 0; i < list.length; i++) {
|
||||||
|
ret.push(tryJSONparse(list[i]));
|
||||||
|
}
|
||||||
|
ret = _.compact(ret);
|
||||||
|
return ret.length === 0 ? false : ret;
|
||||||
|
|
||||||
|
function tryJSONparse(str) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(str);
|
||||||
|
} catch(e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.diffTime = function(time) {
|
||||||
|
var diff = process.hrtime(time);
|
||||||
|
return (diff[0] * 1E9 + diff[1])/(1E9 * 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = exports;
|
||||||
246
lib/services/wallet/validators.js
Normal file
246
lib/services/wallet/validators.js
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var bitcore = require('bitcore-lib');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var utils = require('./utils');
|
||||||
|
|
||||||
|
var MAX_INT = 0xffffffff; // Math.pow(2, 32) - 1
|
||||||
|
|
||||||
|
exports.sanitizeRangeOptions = function(options) {
|
||||||
|
if (!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
options.height = options.height || 0;
|
||||||
|
options.index = options.index || 0;
|
||||||
|
|
||||||
|
if (!options.limit) {
|
||||||
|
options.limit = 10;
|
||||||
|
} else if (options.limit > 500) {
|
||||||
|
throw new Error('Limit exceeds maximum');
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(bitcore.util.js.isNaturalNumber(options.height), '"height" is expected to be a natural number');
|
||||||
|
assert(bitcore.util.js.isNaturalNumber(options.index), '"index" is expected to be a natural number');
|
||||||
|
assert(bitcore.util.js.isNaturalNumber(options.limit), '"limit" is expected to be a natural number');
|
||||||
|
|
||||||
|
assert(options.limit <= 500, '"limit" exceeds maximum');
|
||||||
|
|
||||||
|
if (options.end) {
|
||||||
|
assert(bitcore.util.js.isNaturalNumber(options.end.height), '"end height" is expected to be a natural number');
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkRangeParams = function(req, res, next) {
|
||||||
|
assert(req.bitcoinHeight, '"bitcoinHeight" is expected to be set on the request');
|
||||||
|
|
||||||
|
var range = {
|
||||||
|
height: parseInt(req.query.height),
|
||||||
|
index: parseInt(req.query.index),
|
||||||
|
limit: parseInt(req.query.limit),
|
||||||
|
end: {
|
||||||
|
height: req.bitcoinHeight,
|
||||||
|
index: MAX_INT
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (req.query.end) {
|
||||||
|
range.end.height = parseInt(req.query.end) || req.bitcoinHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
range = exports.sanitizeRangeOptions(range);
|
||||||
|
} catch(e) {
|
||||||
|
return utils.sendError({
|
||||||
|
message: 'Invalid params: ' + e.message,
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(range.height <= range.end.height, '\'Height\' param required to be less than \'End\' param.');
|
||||||
|
req.range = range;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkAddress = function(req, res, next) {
|
||||||
|
var address;
|
||||||
|
var addressStr;
|
||||||
|
|
||||||
|
if (req.body.address) {
|
||||||
|
addressStr = req.body.address;
|
||||||
|
} else {
|
||||||
|
addressStr = req.params.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!addressStr) {
|
||||||
|
return utils.sendError({
|
||||||
|
message: 'Address param is expected',
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(req.network, '"network" is expected to be set on the request');
|
||||||
|
|
||||||
|
try {
|
||||||
|
address = new bitcore.Address(addressStr, req.network);
|
||||||
|
} catch(e) {
|
||||||
|
return utils.sendError({
|
||||||
|
message: 'Invalid address: ' + e.message,
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.address = address;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
//exports.checkAddresses = function(req, res, next) {
|
||||||
|
// var addresses = [];
|
||||||
|
//
|
||||||
|
// if (!req.body.addresses || !req.body.addresses.length || !Array.isArray(req.body.addresses)) {
|
||||||
|
// return utils.sendError({
|
||||||
|
// message: 'Addresses param is expected',
|
||||||
|
// statusCode: 400
|
||||||
|
// }, res);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// assert(req.network, '"network" is expected to be set on the request');
|
||||||
|
//
|
||||||
|
// for (var i = 0; i < req.body.addresses.length; i++) {
|
||||||
|
// var address;
|
||||||
|
// try {
|
||||||
|
// address = new bitcore.Address(req.body.addresses[i], req.network);
|
||||||
|
// } catch(e) {
|
||||||
|
// return utils.sendError({
|
||||||
|
// message: 'Invalid address: ' + e.message,
|
||||||
|
// statusCode: 400
|
||||||
|
// }, res);
|
||||||
|
// }
|
||||||
|
// addresses.push(address);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// req.addresses = addresses;
|
||||||
|
// next();
|
||||||
|
//};
|
||||||
|
|
||||||
|
exports.checkWalletId = function(req, res, next) {
|
||||||
|
|
||||||
|
if (!req.params.walletId) {
|
||||||
|
return utils.sendError({
|
||||||
|
message: 'Wallet id is expected',
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.params.walletId.length !== 64 || !bitcore.util.js.isHexa(req.params.walletId)) {
|
||||||
|
return utils.sendError({
|
||||||
|
message: 'Wallet id is expected to be a hexadecimal string with length of 64',
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.walletId = new Buffer(req.params.walletId, 'hex');
|
||||||
|
next();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkAddresses = function(req, res, next) {
|
||||||
|
|
||||||
|
if (!req.file || !req.file.buffer) {
|
||||||
|
generateError(406, 'Content-Type must be set to multipart/form' +
|
||||||
|
' and addresses key and value must be given.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var buf = req.file.buffer;
|
||||||
|
var bufString = buf.toString();
|
||||||
|
req.addresses = parse(bufString);
|
||||||
|
if (!req.addresses) {
|
||||||
|
generateError(415, 'Could not parse addresses buffer into something meaningful.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
|
||||||
|
function generateError(status, msg) {
|
||||||
|
res.status(status).jsonp({
|
||||||
|
error: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//we are able to deal with json/jsonl, possibly others
|
||||||
|
function parse(string) {
|
||||||
|
var ret = false;
|
||||||
|
var delims = [null, '\n', ' '];
|
||||||
|
for(var i = 0; i < delims.length; i++) {
|
||||||
|
ret = utils.delimitedStringParse(delims[i], string);
|
||||||
|
if (_.isArray(ret)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkAuthHeaders = function(req, res) {
|
||||||
|
var identity = req.header('x-identity');
|
||||||
|
var signature = req.header('x-signature');
|
||||||
|
var nonce = req.header('x-nonce');
|
||||||
|
if (identity && (identity.length > 130 || !bitcore.util.js.isHexa(identity))) {
|
||||||
|
utils.sendError({
|
||||||
|
message: 'x-identity is expected to be a hexadecimal string with length of less than 131',
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (signature && (signature.length > 142 || !bitcore.util.js.isHexa(signature))) {
|
||||||
|
utils.sendError({
|
||||||
|
message: 'x-signature is expected to be a hexadecimal string with length of less than 143',
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nonce && (nonce.length > 128 || nonce.length % 2 !== 0 || !bitcore.util.js.isHexa(nonce))) {
|
||||||
|
utils.sendError({
|
||||||
|
message: 'x-nonce is expected to be a hexadecimal string with length of less than 129',
|
||||||
|
statusCode: 400
|
||||||
|
}, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkDate = function(dateStrings) {
|
||||||
|
var errors = [];
|
||||||
|
if (!Array.isArray(dateStrings)) {
|
||||||
|
dateStrings = [dateStrings];
|
||||||
|
}
|
||||||
|
for(var i = 0; i < dateStrings.length; i++) {
|
||||||
|
internalDateCheck(dateStrings[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function internalDateCheck(dateString) {
|
||||||
|
var date = new Date(utils.toIntIfNumberLike(dateString));
|
||||||
|
if (date.toString() === 'Invalid Date') {
|
||||||
|
errors.push('The date supplied: \'' + dateString +
|
||||||
|
'\' is not a valid date string. A valid date could be: \'2016-09-01\'.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkDateFunction = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
return function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
var errors = self.checkDate([args[1], args[2]]);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
args.unshift(errors);
|
||||||
|
} else {
|
||||||
|
args.unshift(null);
|
||||||
|
}
|
||||||
|
callback.apply(null, args);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exports;
|
||||||
@ -52,14 +52,18 @@
|
|||||||
"commander": "^2.8.1",
|
"commander": "^2.8.1",
|
||||||
"errno": "^0.1.4",
|
"errno": "^0.1.4",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
|
"leveldown": "",
|
||||||
|
"levelup": "^1.3.3",
|
||||||
"liftoff": "^2.2.0",
|
"liftoff": "^2.2.0",
|
||||||
"lmdb": "rvagg/lmdb#5ad819f77714925248dfecb0ba2174080dba949f",
|
"lodash": "^4.17.4",
|
||||||
"lru-cache": "^4.0.1",
|
"lru-cache": "^4.0.1",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
|
"multer": "^1.2.1",
|
||||||
"path-is-absolute": "^1.0.0",
|
"path-is-absolute": "^1.0.0",
|
||||||
"semver": "^5.0.1",
|
"semver": "^5.0.1",
|
||||||
"socket.io": "^1.4.5",
|
"socket.io": "^1.4.5",
|
||||||
"socket.io-client": "^1.4.5",
|
"socket.io-client": "^1.4.5",
|
||||||
|
"ttyread": "^1.0.2",
|
||||||
"zmq": "^2.14.0"
|
"zmq": "^2.14.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user