api keys.
This commit is contained in:
parent
2b41cfb9bd
commit
4a8768bcd2
@ -45,7 +45,6 @@ var USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)'
|
|||||||
function request(options, callback, stream) {
|
function request(options, callback, stream) {
|
||||||
var qs = require('querystring');
|
var qs = require('querystring');
|
||||||
var url = require('url');
|
var url = require('url');
|
||||||
|
|
||||||
var uri = options.uri;
|
var uri = options.uri;
|
||||||
var query = options.query;
|
var query = options.query;
|
||||||
var body = options.body;
|
var body = options.body;
|
||||||
|
|||||||
@ -43,6 +43,16 @@ function HTTPServer(options) {
|
|||||||
this.pool = this.node.pool;
|
this.pool = this.node.pool;
|
||||||
this.logger = options.logger || this.node.logger;
|
this.logger = options.logger || this.node.logger;
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
|
this.apiKey = options.apiKey;
|
||||||
|
|
||||||
|
if (this.apiKey) {
|
||||||
|
if (typeof this.apiKey === 'string') {
|
||||||
|
assert(utils.isHex(this.apiKey), 'API key must be a hex string.');
|
||||||
|
this.apiKey = new Buffer(this.apiKey, 'hex');
|
||||||
|
}
|
||||||
|
assert(Buffer.isBuffer(this.apiKey));
|
||||||
|
assert(this.apiKey.length === 32, 'API key must be 32 bytes.');
|
||||||
|
}
|
||||||
|
|
||||||
options.sockets = true;
|
options.sockets = true;
|
||||||
|
|
||||||
@ -62,8 +72,8 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.server.on('request', function(req, res) {
|
this.server.on('request', function(req, res) {
|
||||||
self.logger.debug('Request from %s path=%s',
|
self.logger.debug('Request for path=%s (%s).',
|
||||||
req.socket.remoteAddress, req.pathname);
|
req.pathname, req.socket.remoteAddress);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.use(function(req, res, next, send) {
|
this.use(function(req, res, next, send) {
|
||||||
@ -87,6 +97,30 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.use(function(req, res, next, send) {
|
||||||
|
var auth = req.headers['authorization'];
|
||||||
|
var parts;
|
||||||
|
|
||||||
|
if (!auth) {
|
||||||
|
req.username = null;
|
||||||
|
req.password = null;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = auth.split(' ');
|
||||||
|
assert(parts.length === 2, 'Invalid auth token.');
|
||||||
|
assert(parts[0] === 'Basic', 'Invalid auth token.');
|
||||||
|
|
||||||
|
auth = new Buffer(parts[1], 'base64').toString('utf8');
|
||||||
|
parts = auth.split(':');
|
||||||
|
assert(parts.length >= 2, 'Invalid auth token.');
|
||||||
|
|
||||||
|
req.username = parts.shift();
|
||||||
|
req.password = parts.join(':');
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
this.use(function(req, res, next, send) {
|
this.use(function(req, res, next, send) {
|
||||||
var params = utils.merge({}, req.params, req.query, req.body);
|
var params = utils.merge({}, req.params, req.query, req.body);
|
||||||
var options = {};
|
var options = {};
|
||||||
@ -184,16 +218,52 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
if (params.passphrase)
|
if (params.passphrase)
|
||||||
options.passphrase = params.passphrase;
|
options.passphrase = params.passphrase;
|
||||||
|
|
||||||
|
if (req.password) {
|
||||||
|
assert(utils.isHex(req.password), 'API key must be a hex string.');
|
||||||
|
assert(req.password.length === 64, 'API key must be 32 bytes.');
|
||||||
|
options.apiKey = new Buffer(req.password, 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
req.options = options;
|
req.options = options;
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.use(function(req, res, next, send) {
|
||||||
|
if (req.path.length < 2 || req.path[0] !== 'wallet') {
|
||||||
|
if (self.apiKey) {
|
||||||
|
if (!utils.ccmp(req.options.apiKey, self.apiKey)) {
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="node"');
|
||||||
|
send(401, { error: 'Unauthorized.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.options.auth)
|
||||||
|
return next();
|
||||||
|
|
||||||
|
self.walletdb.auth(req.options.id, req.options.apiKey, function(err) {
|
||||||
|
if (err) {
|
||||||
|
if (err.message === 'Wallet not found.')
|
||||||
|
return next();
|
||||||
|
self.logger.info('Auth failure for %s: %s.',
|
||||||
|
req.options.id, err.message);
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="wallet"');
|
||||||
|
send(401, { error: err.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function decodeScript(script) {
|
function decodeScript(script) {
|
||||||
if (!script)
|
if (!script)
|
||||||
return;
|
return;
|
||||||
if (typeof script === 'string')
|
if (typeof script === 'string')
|
||||||
return new bcoin.script(new Buffer(script, 'hex'));
|
return bcoin.script.fromRaw(script, 'hex');
|
||||||
return new bcoin.script(script);
|
return new bcoin.script(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,9 +426,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
send(200, {
|
send(200, { success: true });
|
||||||
success: true
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -421,6 +489,42 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Change passphrase
|
||||||
|
this.post('/wallet/:id/passphrase', function(req, res, next, send) {
|
||||||
|
var id = req.options.id;
|
||||||
|
var options = req.options;
|
||||||
|
var old = options.old;
|
||||||
|
var new_ = options.passphrase;
|
||||||
|
self.walletdb.setPassphrase(id, old, _new, function(err) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
send(200, { success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate new token
|
||||||
|
this.post('/wallet/:id/retoken', function(req, res, next, send) {
|
||||||
|
var id = req.options.id;
|
||||||
|
var options = req.options;
|
||||||
|
self.walletdb.retoken(id, options.passphrase, function(err, token) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
send(200, { token: token.toString('hex') });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Broadcast TX
|
||||||
|
this.post('/wallet/:id/broadcast', function(req, res, next, send) {
|
||||||
|
self.node.sendTX(req.options.tx, function(err) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
send(200, { success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Send TX
|
// Send TX
|
||||||
this.post('/wallet/:id/send', function(req, res, next, send) {
|
this.post('/wallet/:id/send', function(req, res, next, send) {
|
||||||
var id = req.options.id;
|
var id = req.options.id;
|
||||||
@ -498,9 +602,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
send(200, {
|
send(200, { success: true });
|
||||||
success: true
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -731,8 +833,27 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
self.emit('error', err);
|
self.emit('error', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('join', function(id) {
|
socket.on('join', function(id, apiKey) {
|
||||||
socket.join(id);
|
if (!self.options.auth) {
|
||||||
|
socket.join(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id === '!all') {
|
||||||
|
if (self.apiKey) {
|
||||||
|
if (!utils.ccmp(apiKey, self.apiKey)) {
|
||||||
|
self.logger.info('Auth failure for %s.', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return socket.join(id);
|
||||||
|
}
|
||||||
|
self.walletdb.auth(id, apiKey, function(err) {
|
||||||
|
if (err) {
|
||||||
|
self.logger.info('Auth failure for %s: %s.', id, err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.join(id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('leave', function(id) {
|
socket.on('leave', function(id) {
|
||||||
@ -748,37 +869,30 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.walletdb.on('tx', function(tx, map) {
|
this.walletdb.on('tx', function(tx, map) {
|
||||||
|
var summary = map.toJSON();
|
||||||
tx = tx.toJSON();
|
tx = tx.toJSON();
|
||||||
map.all.forEach(function(id) {
|
map.getWallets().forEach(function(id) {
|
||||||
self.server.io.to(id).emit('tx', tx);
|
self.server.io.to(id).emit('tx', tx, summary);
|
||||||
});
|
});
|
||||||
self.server.io.to('!all').emit('tx', tx, map);
|
self.server.io.to('!all').emit('tx', tx, summary);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.walletdb.on('confirmed', function(tx, map) {
|
this.walletdb.on('confirmed', function(tx, map) {
|
||||||
|
var summary = map.toJSON();
|
||||||
tx = tx.toJSON();
|
tx = tx.toJSON();
|
||||||
map.all.forEach(function(id) {
|
map.getWallets().forEach(function(id) {
|
||||||
self.server.io.to(id).emit('confirmed', tx);
|
self.server.io.to(id).emit('confirmed', tx, summary);
|
||||||
});
|
});
|
||||||
self.server.io.to('!all').emit('confirmed', tx, map);
|
self.server.io.to('!all').emit('confirmed', tx, summary);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.walletdb.on('updated', function(tx, map) {
|
this.walletdb.on('updated', function(tx, map) {
|
||||||
|
var summary = map.toJSON();
|
||||||
tx = tx.toJSON();
|
tx = tx.toJSON();
|
||||||
map.all.forEach(function(id) {
|
map.getWallets().forEach(function(id) {
|
||||||
self.server.io.to(id).emit('updated', tx);
|
self.server.io.to(id).emit('updated', tx, summary);
|
||||||
});
|
});
|
||||||
self.server.io.to('!all').emit('updated', tx, map);
|
self.server.io.to('!all').emit('updated', tx, summary);
|
||||||
});
|
|
||||||
|
|
||||||
this.walletdb.on('balance', function(balance, id) {
|
|
||||||
var json = {
|
|
||||||
confirmed: utils.btc(balance.confirmed),
|
|
||||||
unconfirmed: utils.btc(balance.unconfirmed),
|
|
||||||
total: utils.btc(balance.total)
|
|
||||||
};
|
|
||||||
self.server.io.to(id).emit('balance', json);
|
|
||||||
self.server.io.to('!all').emit('balance', json, id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.walletdb.on('balances', function(balances) {
|
this.walletdb.on('balances', function(balances) {
|
||||||
@ -789,11 +903,15 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
unconfirmed: utils.btc(balances[id].unconfirmed),
|
unconfirmed: utils.btc(balances[id].unconfirmed),
|
||||||
total: utils.btc(balances[id].total)
|
total: utils.btc(balances[id].total)
|
||||||
};
|
};
|
||||||
|
self.server.io.to(id).emit('balance', json[id], id);
|
||||||
|
self.server.io.to('!all').emit('balance', json[id], id);
|
||||||
});
|
});
|
||||||
self.server.io.to('!all').emit('balances', json);
|
self.server.io.to('!all').emit('balances', json);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.walletdb.on('address', function(receive, change, map) {
|
this.walletdb.on('address', function(receive, change, map) {
|
||||||
|
var summary = map.toJSON();
|
||||||
|
|
||||||
if (receive) {
|
if (receive) {
|
||||||
receive = receive.map(function(address) {
|
receive = receive.map(function(address) {
|
||||||
return address.toJSON();
|
return address.toJSON();
|
||||||
@ -806,11 +924,11 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
map.all.forEach(function(id) {
|
map.getWallets().forEach(function(id) {
|
||||||
self.server.io.to(id).emit('address', receive, change, map);
|
self.server.io.to(id).emit('address', receive, change, summary);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.server.io.to('!all').emit('address', receive, change, map);
|
self.server.io.to('!all').emit('address', receive, change, summary);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2031,6 +2031,58 @@ WalletMap.prototype.hasPaths = function hasPaths(address) {
|
|||||||
return paths && paths.length !== 0;
|
return paths && paths.length !== 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a unique list of wallet IDs for the map.
|
||||||
|
* @returns {WalletID[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
WalletMap.prototype.getWallets = function getWallets() {
|
||||||
|
var ids = {};
|
||||||
|
var i, member;
|
||||||
|
|
||||||
|
for (i = 0; i < this.accounts.length; i++) {
|
||||||
|
member = this.accounts[i];
|
||||||
|
ids[member.id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(ids);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a unique list of wallet IDs for the map.
|
||||||
|
* @returns {WalletID[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
WalletMap.prototype.getInputWallets = function getInputWallets() {
|
||||||
|
var ids = {};
|
||||||
|
var i, member;
|
||||||
|
|
||||||
|
for (i = 0; i < this.inputs.length; i++) {
|
||||||
|
member = this.inputs[i];
|
||||||
|
ids[member.id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(ids);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a unique list of wallet IDs for the map.
|
||||||
|
* @returns {WalletID[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
WalletMap.prototype.getOutputWallets = function getOutputWallets() {
|
||||||
|
var ids = {};
|
||||||
|
var i, member;
|
||||||
|
|
||||||
|
for (i = 0; i < this.outputs.length; i++) {
|
||||||
|
member = this.outputs[i];
|
||||||
|
ids[member.id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(ids);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get paths for a given address hash.
|
* Get paths for a given address hash.
|
||||||
* @param {Hash} address
|
* @param {Hash} address
|
||||||
|
|||||||
@ -1928,6 +1928,11 @@ utils.ccmp = function ccmp(a, b) {
|
|||||||
if (!Buffer.isBuffer(b))
|
if (!Buffer.isBuffer(b))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// It's assumed the target length
|
||||||
|
// would be known to an attacker anyway.
|
||||||
|
if (a.length !== b.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
for (i = 0; i < a.length; i++)
|
for (i = 0; i < a.length; i++)
|
||||||
res |= a[i] ^ b[i];
|
res |= a[i] ^ b[i];
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
var bcoin = require('./env');
|
var bcoin = require('./env');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var constants = bcoin.protocol.constants;
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
var assert = utils.assert;
|
var assert = utils.assert;
|
||||||
var BufferReader = require('./reader');
|
var BufferReader = require('./reader');
|
||||||
@ -55,6 +56,8 @@ function Wallet(db, options) {
|
|||||||
this.master = null;
|
this.master = null;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.accountDepth = 0;
|
this.accountDepth = 0;
|
||||||
|
this.token = constants.ZERO_HASH;
|
||||||
|
this.tokenDepth = 0;
|
||||||
|
|
||||||
this.account = null;
|
this.account = null;
|
||||||
|
|
||||||
@ -72,6 +75,7 @@ utils.inherits(Wallet, EventEmitter);
|
|||||||
|
|
||||||
Wallet.prototype.fromOptions = function fromOptions(options) {
|
Wallet.prototype.fromOptions = function fromOptions(options) {
|
||||||
var master = options.master;
|
var master = options.master;
|
||||||
|
var id, token;
|
||||||
|
|
||||||
if (!master)
|
if (!master)
|
||||||
master = bcoin.hd.fromMnemonic(null, this.network);
|
master = bcoin.hd.fromMnemonic(null, this.network);
|
||||||
@ -79,9 +83,11 @@ Wallet.prototype.fromOptions = function fromOptions(options) {
|
|||||||
if (!bcoin.hd.isHD(master) && !MasterKey.isMasterKey(master))
|
if (!bcoin.hd.isHD(master) && !MasterKey.isMasterKey(master))
|
||||||
master = bcoin.hd.from(master, this.network);
|
master = bcoin.hd.from(master, this.network);
|
||||||
|
|
||||||
if (!MasterKey.isMasterKey(master))
|
if (bcoin.hd.isHD(master))
|
||||||
master = MasterKey.fromKey(master);
|
master = MasterKey.fromKey(master);
|
||||||
|
|
||||||
|
assert(MasterKey.isMasterKey(master));
|
||||||
|
|
||||||
this.master = master;
|
this.master = master;
|
||||||
|
|
||||||
if (options.initialized != null) {
|
if (options.initialized != null) {
|
||||||
@ -96,11 +102,28 @@ Wallet.prototype.fromOptions = function fromOptions(options) {
|
|||||||
|
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
assert(utils.isAlpha(options.id), 'Wallet ID must be alphanumeric.');
|
assert(utils.isAlpha(options.id), 'Wallet ID must be alphanumeric.');
|
||||||
this.id = options.id;
|
id = options.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.id)
|
if (!id)
|
||||||
this.id = this.getID();
|
id = this.getID();
|
||||||
|
|
||||||
|
if (options.token) {
|
||||||
|
assert(Buffer.isBuffer(options.token));
|
||||||
|
assert(options.token.length === 32);
|
||||||
|
token = options.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.tokenDepth != null) {
|
||||||
|
assert(utils.isNumber(options.tokenDepth));
|
||||||
|
this.tokenDepth = options.tokenDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token)
|
||||||
|
token = this.getToken(this.master.key, this.tokenDepth);
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.token = token;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -294,6 +317,43 @@ Wallet.prototype.setPassphrase = function setPassphrase(old, new_, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new token.
|
||||||
|
* @param {(String|Buffer)?} passphrase
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
|
||||||
|
Wallet.prototype.retoken = function retoken(passphrase, callback) {
|
||||||
|
var self = this;
|
||||||
|
var unlock;
|
||||||
|
|
||||||
|
if (typeof passphrase === 'function') {
|
||||||
|
callback = passphrase;
|
||||||
|
passphrase = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock = this.locker.lock(retoken, [passphrase, callback]);
|
||||||
|
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
|
this.unlock(passphrase, null, function(err, master) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.tokenDepth++;
|
||||||
|
self.token = self.getToken(master, self.tokenDepth);
|
||||||
|
|
||||||
|
self.save(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
return callback(null, self.token);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock the wallet, destroy decrypted key.
|
* Lock the wallet, destroy decrypted key.
|
||||||
*/
|
*/
|
||||||
@ -314,7 +374,7 @@ Wallet.prototype.unlock = function unlock(passphrase, timeout, callback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the wallet ID if none was passed in.
|
* Generate the wallet ID if none was passed in.
|
||||||
* It is represented as `m/44'` (public) hashed
|
* It is represented as `m/44` (public) hashed
|
||||||
* and converted to an address with a prefix
|
* and converted to an address with a prefix
|
||||||
* of `0x03be04` (`WLT` in base58).
|
* of `0x03be04` (`WLT` in base58).
|
||||||
* @returns {Base58String}
|
* @returns {Base58String}
|
||||||
@ -325,7 +385,7 @@ Wallet.prototype.getID = function getID() {
|
|||||||
|
|
||||||
assert(this.master.key, 'Cannot derive id.');
|
assert(this.master.key, 'Cannot derive id.');
|
||||||
|
|
||||||
key = this.master.key.derive(44, true);
|
key = this.master.key.derive(44);
|
||||||
|
|
||||||
p = new BufferWriter();
|
p = new BufferWriter();
|
||||||
p.writeU8(0x03);
|
p.writeU8(0x03);
|
||||||
@ -337,6 +397,27 @@ Wallet.prototype.getID = function getID() {
|
|||||||
return utils.toBase58(p.render());
|
return utils.toBase58(p.render());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the wallet api key if none was passed in.
|
||||||
|
* It is represented as HASH256(m/44'->public|nonce).
|
||||||
|
* @param {Number} nonce
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Wallet.prototype.getToken = function getToken(master, nonce) {
|
||||||
|
var key, p;
|
||||||
|
|
||||||
|
assert(master, 'Cannot derive token.');
|
||||||
|
|
||||||
|
key = master.derive(44, true);
|
||||||
|
|
||||||
|
p = new BufferWriter();
|
||||||
|
p.writeBytes(key.publicKey);
|
||||||
|
p.writeU32(nonce);
|
||||||
|
|
||||||
|
return utils.hash256(p.render());
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an account. Requires passphrase if master key is encrypted.
|
* Create an account. Requires passphrase if master key is encrypted.
|
||||||
* @param {Object} options - See {@link Account} options.
|
* @param {Object} options - See {@link Account} options.
|
||||||
@ -1437,6 +1518,8 @@ Wallet.prototype.inspect = function inspect() {
|
|||||||
network: this.network.type,
|
network: this.network.type,
|
||||||
initialized: this.initialized,
|
initialized: this.initialized,
|
||||||
accountDepth: this.accountDepth,
|
accountDepth: this.accountDepth,
|
||||||
|
token: this.token.toString('hex'),
|
||||||
|
tokenDepth: this.tokenDepth,
|
||||||
master: this.master,
|
master: this.master,
|
||||||
account: this.account
|
account: this.account
|
||||||
};
|
};
|
||||||
@ -1455,6 +1538,8 @@ Wallet.prototype.toJSON = function toJSON() {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
initialized: this.initialized,
|
initialized: this.initialized,
|
||||||
accountDepth: this.accountDepth,
|
accountDepth: this.accountDepth,
|
||||||
|
token: this.token.toString('hex'),
|
||||||
|
tokenDepth: this.tokenDepth,
|
||||||
master: this.master.toJSON(),
|
master: this.master.toJSON(),
|
||||||
account: this.account ? this.account.toJSON() : null
|
account: this.account ? this.account.toJSON() : null
|
||||||
};
|
};
|
||||||
@ -1470,11 +1555,15 @@ Wallet.prototype.fromJSON = function fromJSON(json) {
|
|||||||
assert(utils.isAlpha(json.id), 'Wallet ID must be alphanumeric.');
|
assert(utils.isAlpha(json.id), 'Wallet ID must be alphanumeric.');
|
||||||
assert(typeof json.initialized === 'boolean');
|
assert(typeof json.initialized === 'boolean');
|
||||||
assert(utils.isNumber(json.accountDepth));
|
assert(utils.isNumber(json.accountDepth));
|
||||||
|
assert(typeof json.token === 'string');
|
||||||
|
assert(json.token.length === 64);
|
||||||
|
assert(utils.isNumber(json.tokenDepth));
|
||||||
|
|
||||||
this.network = bcoin.network.get(json.network);
|
this.network = bcoin.network.get(json.network);
|
||||||
this.id = json.id;
|
this.id = json.id;
|
||||||
this.initialized = json.initialized;
|
this.initialized = json.initialized;
|
||||||
this.accountDepth = json.accountDepth;
|
this.accountDepth = json.accountDepth;
|
||||||
|
this.token = new Buffer(json.token, 'hex');
|
||||||
this.master = MasterKey.fromJSON(json.master);
|
this.master = MasterKey.fromJSON(json.master);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@ -1492,6 +1581,8 @@ Wallet.prototype.toRaw = function toRaw(writer) {
|
|||||||
p.writeVarString(this.id, 'utf8');
|
p.writeVarString(this.id, 'utf8');
|
||||||
p.writeU8(this.initialized ? 1 : 0);
|
p.writeU8(this.initialized ? 1 : 0);
|
||||||
p.writeU32(this.accountDepth);
|
p.writeU32(this.accountDepth);
|
||||||
|
p.writeBytes(this.token);
|
||||||
|
p.writeU32(this.tokenDepth);
|
||||||
p.writeVarBytes(this.master.toRaw());
|
p.writeVarBytes(this.master.toRaw());
|
||||||
|
|
||||||
if (!writer)
|
if (!writer)
|
||||||
@ -1512,6 +1603,8 @@ Wallet.prototype.fromRaw = function fromRaw(data) {
|
|||||||
this.id = p.readVarString('utf8');
|
this.id = p.readVarString('utf8');
|
||||||
this.initialized = p.readU8() === 1;
|
this.initialized = p.readU8() === 1;
|
||||||
this.accountDepth = p.readU32();
|
this.accountDepth = p.readU32();
|
||||||
|
this.token = p.readBytes(32);
|
||||||
|
this.tokenDepth = p.readU32();
|
||||||
this.master = MasterKey.fromRaw(p.readVarBytes());
|
this.master = MasterKey.fromRaw(p.readVarBytes());
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -1585,8 +1678,6 @@ function Account(db, options) {
|
|||||||
this.network = db.network;
|
this.network = db.network;
|
||||||
this.lookahead = Account.MAX_LOOKAHEAD;
|
this.lookahead = Account.MAX_LOOKAHEAD;
|
||||||
|
|
||||||
this.loaded = false;
|
|
||||||
this.loading = false;
|
|
||||||
this.receiveAddress = null;
|
this.receiveAddress = null;
|
||||||
this.changeAddress = null;
|
this.changeAddress = null;
|
||||||
|
|
||||||
@ -2268,13 +2359,13 @@ Account.prototype.toJSON = function toJSON() {
|
|||||||
receiveDepth: this.receiveDepth,
|
receiveDepth: this.receiveDepth,
|
||||||
changeDepth: this.changeDepth,
|
changeDepth: this.changeDepth,
|
||||||
receiveAddress: this.receiveAddress
|
receiveAddress: this.receiveAddress
|
||||||
? this.receiveAddress.getAddress()
|
? this.receiveAddress.getAddress('base58')
|
||||||
: null,
|
: null,
|
||||||
programAddress: this.receiveAddress
|
programAddress: this.receiveAddress
|
||||||
? this.receiveAddress.getProgramAddress()
|
? this.receiveAddress.getProgramAddress('base58')
|
||||||
: null,
|
: null,
|
||||||
changeAddress: this.changeAddress
|
changeAddress: this.changeAddress
|
||||||
? this.changeAddress.getAddress()
|
? this.changeAddress.getAddress('base58')
|
||||||
: null,
|
: null,
|
||||||
accountKey: this.accountKey.xpubkey,
|
accountKey: this.accountKey.xpubkey,
|
||||||
keys: this.keys.map(function(key) {
|
keys: this.keys.map(function(key) {
|
||||||
@ -2469,7 +2560,7 @@ MasterKey.prototype.fromOptions = function fromOptions(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.key) {
|
if (options.key) {
|
||||||
assert(Buffer.isBuffer(options.key));
|
assert(bcoin.hd.isHD(options.key));
|
||||||
this.key = options.key;
|
this.key = options.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -185,17 +185,12 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
|
|||||||
var balances = {};
|
var balances = {};
|
||||||
var i, id, keys;
|
var i, id, keys;
|
||||||
|
|
||||||
utils.forEachSerial(map.outputs, function(output, next) {
|
utils.forEachSerial(map.getOutputWallets(), function(id, next) {
|
||||||
id = output.id;
|
if (self.listeners('balances').length === 0
|
||||||
|
|
||||||
if (self.listeners('balance').length === 0
|
|
||||||
&& !self.hasListener(id, 'balance')) {
|
&& !self.hasListener(id, 'balance')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (balances[id] != null)
|
|
||||||
return next();
|
|
||||||
|
|
||||||
self.getBalance(id, function(err, balance) {
|
self.getBalance(id, function(err, balance) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -215,7 +210,7 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
|
|||||||
self.fire(id, 'balance', balances[id]);
|
self.fire(id, 'balance', balances[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emit('balance', balances, map);
|
self.emit('balances', balances, map);
|
||||||
|
|
||||||
return callback(null, balances);
|
return callback(null, balances);
|
||||||
});
|
});
|
||||||
@ -231,11 +226,8 @@ WalletDB.prototype.updateBalances = function updateBalances(tx, map, callback) {
|
|||||||
|
|
||||||
WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) {
|
WalletDB.prototype.syncOutputs = function syncOutputs(tx, map, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var id;
|
|
||||||
|
|
||||||
utils.forEachSerial(map.outputs, function(output, next) {
|
|
||||||
id = output.id;
|
|
||||||
|
|
||||||
|
utils.forEachSerial(map.getOutputWallets(), function(id, next) {
|
||||||
self.syncOutputDepth(id, tx, function(err, receive, change) {
|
self.syncOutputDepth(id, tx, function(err, receive, change) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -474,6 +466,46 @@ WalletDB.prototype.save = function save(wallet, callback) {
|
|||||||
this.db.put('w/' + wallet.id, wallet.toRaw(), callback);
|
this.db.put('w/' + wallet.id, wallet.toRaw(), callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test an api key against a wallet's api key.
|
||||||
|
* @param {WalletID} id
|
||||||
|
* @param {String} token
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
|
||||||
|
WalletDB.prototype.auth = function auth(id, token, callback) {
|
||||||
|
var wallet;
|
||||||
|
|
||||||
|
if (!id)
|
||||||
|
return callback(new Error('Wallet not found.'));
|
||||||
|
|
||||||
|
this.db.get('w/' + id, function(err, data) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return callback(new Error('Wallet not found.'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
wallet = bcoin.wallet.fromRaw(self, data);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof token === 'string') {
|
||||||
|
if (!utils.isHex(token))
|
||||||
|
return callback(new Error('Authentication error.'));
|
||||||
|
token = new Buffer(token, 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare in constant time:
|
||||||
|
if (!utils.ccmp(token, wallet.token))
|
||||||
|
return callback(new Error('Authentication error.'));
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new wallet, save to database, setup watcher.
|
* Create a new wallet, save to database, setup watcher.
|
||||||
* @param {Object} options - See {@link Wallet}.
|
* @param {Object} options - See {@link Wallet}.
|
||||||
@ -613,6 +645,9 @@ WalletDB.prototype.getAccount = function getAccount(id, name, callback) {
|
|||||||
WalletDB.prototype.getAccounts = function getAccounts(id, callback) {
|
WalletDB.prototype.getAccounts = function getAccounts(id, callback) {
|
||||||
var accounts = [];
|
var accounts = [];
|
||||||
|
|
||||||
|
if (!utils.isAlpha(id))
|
||||||
|
return callback(new Error('Wallet IDs must be alphanumeric.'));
|
||||||
|
|
||||||
this.db.iterate({
|
this.db.iterate({
|
||||||
gte: 'i/' + id + '/',
|
gte: 'i/' + id + '/',
|
||||||
lte: 'i/' + id + '/~',
|
lte: 'i/' + id + '/~',
|
||||||
@ -1184,6 +1219,29 @@ WalletDB.prototype.removeKey = function removeKey(id, name, key, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WalletDB.prototype.setPassphrase = function setPassphrase(id, old, new_, callback) {
|
||||||
|
if (typeof new_ === 'function') {
|
||||||
|
callback = new_;
|
||||||
|
new_ = old;
|
||||||
|
old = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchWallet(id, callback, function(wallet, callback) {
|
||||||
|
wallet.setPassphrase(old, new_, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletDB.prototype.retoken = function retoken(id, passphrase, callback) {
|
||||||
|
if (typeof passphrase === 'function') {
|
||||||
|
callback = passphrase;
|
||||||
|
passphrase = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchWallet(id, callback, function(wallet, callback) {
|
||||||
|
wallet.retoken(passphrase, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
WalletDB.prototype.getInfo = function getInfo(id, callback) {
|
WalletDB.prototype.getInfo = function getInfo(id, callback) {
|
||||||
this.fetchWallet(id, callback, function(wallet, callback) {
|
this.fetchWallet(id, callback, function(wallet, callback) {
|
||||||
callback(null, wallet);
|
callback(null, wallet);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user