master key. use locks to avoid race conditions in wallet.
This commit is contained in:
parent
c3ba9808b1
commit
64af74fe4a
@ -925,7 +925,7 @@ HDPrivateKey.parseBase58 = function parseBase58(xkey) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
HDPrivateKey.parseRaw = function parseRaw(raw) {
|
HDPrivateKey.parseRaw = function parseRaw(raw) {
|
||||||
var p = new BufferReader(raw, true);
|
var p = new BufferReader(raw);
|
||||||
var data = {};
|
var data = {};
|
||||||
var i, type, prefix;
|
var i, type, prefix;
|
||||||
|
|
||||||
@ -1426,7 +1426,7 @@ HDPublicKey.parseBase58 = function parseBase58(xkey) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
HDPublicKey.parseRaw = function parseRaw(raw) {
|
HDPublicKey.parseRaw = function parseRaw(raw) {
|
||||||
var p = new BufferReader(raw, true);
|
var p = new BufferReader(raw);
|
||||||
var data = {};
|
var data = {};
|
||||||
|
|
||||||
data.version = p.readU32BE();
|
data.version = p.readU32BE();
|
||||||
|
|||||||
@ -53,6 +53,7 @@ function Wallet(options) {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
this.network = bcoin.network.get(options.network);
|
this.network = bcoin.network.get(options.network);
|
||||||
this.db = options.db;
|
this.db = options.db;
|
||||||
|
this.locker = new bcoin.locker(this);
|
||||||
|
|
||||||
if (!master)
|
if (!master)
|
||||||
master = bcoin.hd.fromMnemonic(null, this.network);
|
master = bcoin.hd.fromMnemonic(null, this.network);
|
||||||
@ -146,6 +147,8 @@ Wallet.prototype.destroy = function destroy(callback) {
|
|||||||
|
|
||||||
assert(!this.loading);
|
assert(!this.loading);
|
||||||
|
|
||||||
|
this.master.destroy();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.db.unregister(this);
|
this.db.unregister(this);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -219,12 +222,20 @@ Wallet.prototype.init = function init(callback) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Wallet.prototype.addKey = function addKey(account, key, callback) {
|
Wallet.prototype.addKey = function addKey(account, key, callback) {
|
||||||
|
var unlock;
|
||||||
|
|
||||||
if (typeof key === 'function') {
|
if (typeof key === 'function') {
|
||||||
callback = key;
|
callback = key;
|
||||||
key = account;
|
key = account;
|
||||||
account = 0;
|
account = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unlock = this.locker.lock(addKey, [account, key, callback]);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
this.getAccount(account, function(err, account) {
|
this.getAccount(account, function(err, account) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -233,7 +244,7 @@ Wallet.prototype.addKey = function addKey(account, key, callback) {
|
|||||||
return callback(new Error('Account not found.'));
|
return callback(new Error('Account not found.'));
|
||||||
|
|
||||||
account.addKey(key, callback);
|
account.addKey(key, callback);
|
||||||
});
|
}, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -244,12 +255,20 @@ Wallet.prototype.addKey = function addKey(account, key, callback) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
||||||
|
var unlock;
|
||||||
|
|
||||||
if (typeof key === 'function') {
|
if (typeof key === 'function') {
|
||||||
callback = key;
|
callback = key;
|
||||||
key = account;
|
key = account;
|
||||||
account = 0;
|
account = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unlock = this.locker.lock(removeKey, [account, key, callback]);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
this.getAccount(account, function(err, account) {
|
this.getAccount(account, function(err, account) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -258,7 +277,7 @@ Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
|||||||
return callback(new Error('Account not found.'));
|
return callback(new Error('Account not found.'));
|
||||||
|
|
||||||
account.addKey(key, callback);
|
account.addKey(key, callback);
|
||||||
});
|
}, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -294,6 +313,25 @@ Wallet.prototype.setPassphrase = function setPassphrase(old, new_, callback) {
|
|||||||
return this.save(callback);
|
return this.save(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock the wallet, destroy decrypted key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Wallet.prototype.lock = function lock() {
|
||||||
|
this.master.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock the key for `timeout` milliseconds.
|
||||||
|
* @param {Buffer|String} passphrase
|
||||||
|
* @param {Number?} [timeout=60000] - ms.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Wallet.prototype.unlock = function unlock(passphrase, timeout) {
|
||||||
|
this.master.toKey(passphrase, timeout);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -325,12 +363,18 @@ Wallet.prototype.getID = function getID() {
|
|||||||
* @param {Function} callback - Returns [Error, {@link Account}].
|
* @param {Function} callback - Returns [Error, {@link Account}].
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Wallet.prototype.createAccount = function createAccount(options, callback) {
|
Wallet.prototype.createAccount = function createAccount(options, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var master, key;
|
var master, key, unlock;
|
||||||
|
|
||||||
|
unlock = this.locker.lock(createAccount, [options, callback], force);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
master = this.master.toKey(options.passphrase);
|
master = this.master.toKey(options.passphrase, options.timeout);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(e);
|
return callback(e);
|
||||||
}
|
}
|
||||||
@ -364,17 +408,33 @@ Wallet.prototype.createAccount = function createAccount(options, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List account names and indexes from the db.
|
||||||
|
* @param {Function} callback - Returns [Error, Array].
|
||||||
|
*/
|
||||||
|
|
||||||
|
Wallet.prototype.getAccounts = function getAccounts(callback) {
|
||||||
|
this.db.getAccounts(this.id, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an account from the database.
|
* Retrieve an account from the database.
|
||||||
* @param {Number|String} account
|
* @param {Number|String} account
|
||||||
* @param {Function} callback - Returns [Error, {@link Account}].
|
* @param {Function} callback - Returns [Error, {@link Account}].
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Wallet.prototype.getAccount = function getAccount(account, callback) {
|
Wallet.prototype.getAccount = function getAccount(account, callback, force) {
|
||||||
|
var unlock = this.locker.lock(getAccount, [account, callback], force);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
if (this.account) {
|
if (this.account) {
|
||||||
if (account === 0 || account === 'default')
|
if (account === 0 || account === 'default')
|
||||||
return callback(null, this.account);
|
return callback(null, this.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.db.getAccount(this.id, account, callback);
|
return this.db.getAccount(this.id, account, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -414,11 +474,20 @@ Wallet.prototype.createChange = function createChange(account, callback) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Wallet.prototype.createAddress = function createAddress(account, change, callback) {
|
Wallet.prototype.createAddress = function createAddress(account, change, callback) {
|
||||||
|
var unlock;
|
||||||
|
|
||||||
if (typeof change === 'function') {
|
if (typeof change === 'function') {
|
||||||
callback = change;
|
callback = change;
|
||||||
change = account;
|
change = account;
|
||||||
account = 0;
|
account = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unlock = this.locker.lock(createAddress, [account, change, callback]);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
this.getAccount(account, function(err, account) {
|
this.getAccount(account, function(err, account) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -427,7 +496,7 @@ Wallet.prototype.createAddress = function createAddress(account, change, callbac
|
|||||||
return callback(new Error('Account not found.'));
|
return callback(new Error('Account not found.'));
|
||||||
|
|
||||||
account.createAddress(change, callback);
|
account.createAddress(change, callback);
|
||||||
});
|
}, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -767,7 +836,13 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx, callback) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var accounts = {};
|
var accounts = {};
|
||||||
var result = false;
|
var result = false;
|
||||||
var i, path;
|
var i, path, unlock;
|
||||||
|
|
||||||
|
unlock = this.locker.lock(syncOutputDepth, [tx, callback]);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
this.getOutputPaths(tx, function(err, paths) {
|
this.getOutputPaths(tx, function(err, paths) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -825,7 +900,7 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx, callback) {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}, true);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -876,6 +951,7 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Zap stale TXs from wallet (accesses db).
|
* Zap stale TXs from wallet (accesses db).
|
||||||
|
* @param {(Number|String)?} account
|
||||||
* @param {Number} age - Age threshold (unix time, default=72 hours).
|
* @param {Number} age - Age threshold (unix time, default=72 hours).
|
||||||
* @param {Function} callback - Returns [Error].
|
* @param {Function} callback - Returns [Error].
|
||||||
*/
|
*/
|
||||||
@ -906,6 +982,13 @@ Wallet.prototype.zap = function zap(account, age, callback) {
|
|||||||
Wallet.prototype.scan = function scan(getByAddress, callback) {
|
Wallet.prototype.scan = function scan(getByAddress, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var total = 0;
|
var total = 0;
|
||||||
|
var unlock;
|
||||||
|
|
||||||
|
unlock = this.locker.lock(scan, [getByAddress, callback]);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
if (!this.initialized)
|
if (!this.initialized)
|
||||||
return callback(new Error('Wallet is not initialized.'));
|
return callback(new Error('Wallet is not initialized.'));
|
||||||
@ -923,7 +1006,7 @@ Wallet.prototype.scan = function scan(getByAddress, callback) {
|
|||||||
|
|
||||||
total += result;
|
total += result;
|
||||||
|
|
||||||
self.createAccount(self.options, next);
|
self.createAccount(self.options, next, true);
|
||||||
});
|
});
|
||||||
})(null, this.account);
|
})(null, this.account);
|
||||||
};
|
};
|
||||||
@ -990,7 +1073,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
master = self.master.toKey(options.passphrase);
|
master = self.master.toKey(options.passphrase, options.timeout);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(e);
|
return callback(e);
|
||||||
}
|
}
|
||||||
@ -1526,7 +1609,10 @@ Wallet.isWallet = function isWallet(obj) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BIP44 Account
|
* Represents a BIP44 Account belonging to a {@link Wallet}.
|
||||||
|
* Note that this object does not enforce locks. Any method
|
||||||
|
* that does a write is internal API only and will lead
|
||||||
|
* to race conditions if used elsewhere.
|
||||||
* @exports Account
|
* @exports Account
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
@ -1998,11 +2084,11 @@ Account.prototype.deriveAddress = function deriveAddress(change, index) {
|
|||||||
return new bcoin.keyring({
|
return new bcoin.keyring({
|
||||||
network: this.network,
|
network: this.network,
|
||||||
key: key.publicKey,
|
key: key.publicKey,
|
||||||
|
name: this.name,
|
||||||
account: this.accountIndex,
|
account: this.accountIndex,
|
||||||
change: change,
|
change: change,
|
||||||
index: index,
|
index: index,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
name: this.name,
|
|
||||||
witness: this.witness,
|
witness: this.witness,
|
||||||
m: this.m,
|
m: this.m,
|
||||||
n: this.n,
|
n: this.n,
|
||||||
@ -2373,8 +2459,12 @@ Account.isAccount = function isAccount(obj) {
|
|||||||
&& obj.deriveAddress === 'function';
|
&& obj.deriveAddress === 'function';
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Master Key
|
* Master BIP32 key which can exist
|
||||||
|
* in an timed out encrypted state.
|
||||||
|
* @exports Master
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function MasterKey(options) {
|
function MasterKey(options) {
|
||||||
@ -2386,30 +2476,101 @@ function MasterKey(options) {
|
|||||||
this.phrase = options.phrase;
|
this.phrase = options.phrase;
|
||||||
this.passphrase = options.passphrase;
|
this.passphrase = options.passphrase;
|
||||||
this.key = options.key || null;
|
this.key = options.key || null;
|
||||||
|
this.timer = null;
|
||||||
|
this._destroy = this.destroy.bind(this);
|
||||||
|
|
||||||
|
assert(this.encrypted ? !this.key : this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
MasterKey.prototype.toKey = function toKey(passphrase) {
|
/**
|
||||||
|
* Decrypt the key and set a timeout to destroy decrypted data.
|
||||||
|
* @param {Buffer|String} passphrase - Zero this yourself.
|
||||||
|
* @param {Number} [timeout=60000] timeout in ms.
|
||||||
|
* @returns {HDPrivateKey}
|
||||||
|
*/
|
||||||
|
|
||||||
|
MasterKey.prototype.toKey = function toKey(passphrase, timeout) {
|
||||||
|
var self = this;
|
||||||
var xprivkey;
|
var xprivkey;
|
||||||
|
|
||||||
if (this.key)
|
if (!this.key) {
|
||||||
return this.key;
|
assert(this.encrypted);
|
||||||
|
|
||||||
if (this.encrypted) {
|
|
||||||
assert(passphrase, 'Passphrase is required.');
|
|
||||||
xprivkey = utils.decrypt(this.xprivkey, passphrase);
|
xprivkey = utils.decrypt(this.xprivkey, passphrase);
|
||||||
} else {
|
this.key = bcoin.hd.fromRaw(xprivkey);
|
||||||
xprivkey = this.xprivkey;
|
xprivkey.fill(0);
|
||||||
|
this.start(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bcoin.hd.fromRaw(xprivkey);
|
return this.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
MasterKey.prototype.decrypt = function decrypt(passphrase) {
|
/**
|
||||||
if (!this.encrypted)
|
* Start the destroy timer.
|
||||||
|
* @private
|
||||||
|
* @param {Number} [timeout=60000] timeout in ms.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MasterKey.prototype.start = function start(timeout) {
|
||||||
|
if (!timeout)
|
||||||
|
timeout = 60000;
|
||||||
|
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
if (timeout === -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this.timer = setTimeout(this._destroy, timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the destroy timer.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
MasterKey.prototype.stop = function stop() {
|
||||||
|
if (this.timer != null) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the key by zeroing the
|
||||||
|
* privateKey and chainCode. Stop
|
||||||
|
* the timer if there is one.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MasterKey.prototype.destroy = function destroy() {
|
||||||
|
if (!this.encrypted) {
|
||||||
|
assert(this.timer == null);
|
||||||
|
assert(this.key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
if (this.key) {
|
||||||
|
this.key.chainCode.fill(0);
|
||||||
|
this.key.privateKey.fill(0);
|
||||||
|
this.key = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt the key permanently.
|
||||||
|
* @param {Buffer|String} passphrase - Zero this yourself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MasterKey.prototype.decrypt = function decrypt(passphrase) {
|
||||||
|
if (!this.encrypted) {
|
||||||
|
assert(this.key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(passphrase, 'Passphrase is required.');
|
assert(passphrase, 'Passphrase is required.');
|
||||||
|
|
||||||
|
this.destroy();
|
||||||
|
|
||||||
this.encrypted = false;
|
this.encrypted = false;
|
||||||
this.xprivkey = utils.decrypt(this.xprivkey, passphrase);
|
this.xprivkey = utils.decrypt(this.xprivkey, passphrase);
|
||||||
|
|
||||||
@ -2418,10 +2579,19 @@ MasterKey.prototype.decrypt = function decrypt(passphrase) {
|
|||||||
this.passphrase = utils.decrypt(this.passphrase, passphrase);
|
this.passphrase = utils.decrypt(this.passphrase, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.key = this.toKey();
|
this.key = bcoin.hd.fromRaw(this.xprivkey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt the key permanently.
|
||||||
|
* @param {Buffer|String} passphrase - Zero this yourself.
|
||||||
|
*/
|
||||||
|
|
||||||
MasterKey.prototype.encrypt = function encrypt(passphrase) {
|
MasterKey.prototype.encrypt = function encrypt(passphrase) {
|
||||||
|
var xprivkey = this.xprivkey;
|
||||||
|
var phrase = this.phrase;
|
||||||
|
var pass = this.passphrase;
|
||||||
|
|
||||||
if (this.encrypted)
|
if (this.encrypted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -2429,14 +2599,23 @@ MasterKey.prototype.encrypt = function encrypt(passphrase) {
|
|||||||
|
|
||||||
this.key = null;
|
this.key = null;
|
||||||
this.encrypted = true;
|
this.encrypted = true;
|
||||||
this.xprivkey = utils.encrypt(this.xprivkey, passphrase);
|
this.xprivkey = utils.encrypt(xprivkey, passphrase);
|
||||||
|
xprivkey.fill(0);
|
||||||
|
|
||||||
if (this.phrase) {
|
if (this.phrase) {
|
||||||
this.phrase = utils.encrypt(this.phrase, passphrase);
|
this.phrase = utils.encrypt(phrase, passphrase);
|
||||||
this.passphrase = utils.encrypt(this.passphrase, passphrase);
|
this.passphrase = utils.encrypt(pass, passphrase);
|
||||||
|
phrase.fill(0);
|
||||||
|
pass.fill(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize the key in the form of:
|
||||||
|
* `[enc-flag][phrase-marker][phrase?][passphrase?][xprivkey]`
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
MasterKey.prototype.toRaw = function toRaw(writer) {
|
MasterKey.prototype.toRaw = function toRaw(writer) {
|
||||||
var p = new BufferWriter(writer);
|
var p = new BufferWriter(writer);
|
||||||
|
|
||||||
@ -2458,89 +2637,129 @@ MasterKey.prototype.toRaw = function toRaw(writer) {
|
|||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
MasterKey.fromRaw = function fromRaw(raw) {
|
/**
|
||||||
var data = {};
|
* Instantiate master key from serialized data.
|
||||||
var p = new BufferReader(raw);
|
* @returns {MasterKey}
|
||||||
|
*/
|
||||||
|
|
||||||
data.encrypted = p.readU8() === 1;
|
MasterKey.fromRaw = function fromRaw(raw) {
|
||||||
|
var p = new BufferReader(raw);
|
||||||
|
var encrypted, phrase, passphrase, xprivkey, key;
|
||||||
|
|
||||||
|
encrypted = p.readU8() === 1;
|
||||||
|
|
||||||
if (p.readU8() === 1) {
|
if (p.readU8() === 1) {
|
||||||
data.phrase = p.readVarBytes();
|
phrase = p.readVarBytes();
|
||||||
data.passphrase = p.readVarBytes();
|
passphrase = p.readVarBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.xprivkey = p.readBytes(82);
|
xprivkey = p.readBytes(82);
|
||||||
|
|
||||||
if (!data.encrypted)
|
if (!encrypted)
|
||||||
data.key = bcoin.hd.fromRaw(data.xprivkey);
|
key = bcoin.hd.fromRaw(xprivkey);
|
||||||
|
|
||||||
return new MasterKey(data);
|
return new MasterKey({
|
||||||
|
encrypted: encrypted,
|
||||||
|
phrase: phrase,
|
||||||
|
passphrase: passphrase,
|
||||||
|
xprivkey: xprivkey,
|
||||||
|
key: key
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate master key from an HDPrivateKey.
|
||||||
|
* @param {HDPrivateKey} key
|
||||||
|
* @returns {MasterKey}
|
||||||
|
*/
|
||||||
|
|
||||||
MasterKey.fromKey = function fromKey(key) {
|
MasterKey.fromKey = function fromKey(key) {
|
||||||
var data = {};
|
var phrase, passphrase;
|
||||||
|
|
||||||
data.encrypted = false;
|
|
||||||
|
|
||||||
if (key.mnemonic) {
|
if (key.mnemonic) {
|
||||||
data.phrase = new Buffer(key.mnemonic.phrase, 'utf8');
|
phrase = new Buffer(key.mnemonic.phrase, 'utf8');
|
||||||
data.passphrase = new Buffer(key.mnemonic.passphrase, 'utf8');
|
passphrase = new Buffer(key.mnemonic.passphrase, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
data.xprivkey = key.toRaw();
|
return new MasterKey({
|
||||||
|
encrypted: false,
|
||||||
data.key = key;
|
phrase: phrase,
|
||||||
|
passphrase: passphrase,
|
||||||
return new MasterKey(data);
|
xprivkey: key.toRaw(),
|
||||||
|
key: key
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
MasterKey.prototype.toJSON = function toJSON() {
|
/**
|
||||||
var json = {};
|
* Convert master key to a jsonifiable object.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
json.encrypted = this.encrypted;
|
MasterKey.prototype.toJSON = function toJSON() {
|
||||||
|
var phrase, passphrase, xprivkey;
|
||||||
|
|
||||||
if (this.encrypted) {
|
if (this.encrypted) {
|
||||||
if (this.phrase) {
|
if (this.phrase) {
|
||||||
json.phrase = this.phrase.toString('hex');
|
phrase = this.phrase.toString('hex');
|
||||||
json.passphrase = this.passphrase.toString('hex');
|
passphrase = this.passphrase.toString('hex');
|
||||||
}
|
}
|
||||||
json.xprivkey = this.xprivkey.toString('hex');
|
xprivkey = this.xprivkey.toString('hex');
|
||||||
} else {
|
} else {
|
||||||
if (this.phrase) {
|
if (this.phrase) {
|
||||||
json.phrase = this.phrase.toString('utf8');
|
phrase = this.phrase.toString('utf8');
|
||||||
json.passphrase = this.passphrase.toString('utf8');
|
passphrase = this.passphrase.toString('utf8');
|
||||||
}
|
}
|
||||||
json.xprivkey = utils.toBase58(this.xprivkey);
|
xprivkey = utils.toBase58(this.xprivkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return json;
|
return {
|
||||||
|
encrypted: this.encrypted,
|
||||||
|
phrase: phrase,
|
||||||
|
passphrase: passphrase,
|
||||||
|
xprivkey: xprivkey
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
MasterKey.fromJSON = function fromJSON(json) {
|
/**
|
||||||
var data = {};
|
* Instantiate master key from jsonified object.
|
||||||
|
* @returns {MasterKey}
|
||||||
|
*/
|
||||||
|
|
||||||
data.encrypted = json.encrypted;
|
MasterKey.fromJSON = function fromJSON(json) {
|
||||||
|
var phrase, passphrase, xprivkey, key;
|
||||||
|
|
||||||
if (json.encrypted) {
|
if (json.encrypted) {
|
||||||
if (json.phrase) {
|
if (json.phrase) {
|
||||||
data.phrase = new Buffer(json.phrase, 'hex');
|
phrase = new Buffer(json.phrase, 'hex');
|
||||||
data.passphrase = new Buffer(json.passphrase, 'hex');
|
passphrase = new Buffer(json.passphrase, 'hex');
|
||||||
}
|
}
|
||||||
data.xprivkey = new Buffer(json.xprivkey, 'hex');
|
xprivkey = new Buffer(json.xprivkey, 'hex');
|
||||||
} else {
|
} else {
|
||||||
if (json.phrase) {
|
if (json.phrase) {
|
||||||
data.phrase = new Buffer(json.phrase, 'utf8');
|
phrase = new Buffer(json.phrase, 'utf8');
|
||||||
data.passphrase = new Buffer(json.passphrase, 'utf8');
|
passphrase = new Buffer(json.passphrase, 'utf8');
|
||||||
}
|
}
|
||||||
data.xprivkey = utils.fromBase58(json.xprivkey);
|
xprivkey = utils.fromBase58(json.xprivkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.encrypted)
|
if (!json.encrypted)
|
||||||
data.key = bcoin.hd.fromRaw(data.xprivkey);
|
key = bcoin.hd.fromRaw(xprivkey);
|
||||||
|
|
||||||
return new MasterKey(data);
|
return new MasterKey({
|
||||||
|
encrypted: json.encrypted,
|
||||||
|
phrase: phrase,
|
||||||
|
passphrase: passphrase,
|
||||||
|
xprivkey: xprivkey,
|
||||||
|
key: key
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether an object is a MasterKey.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
MasterKey.isMasterKey = function isMasterKey(obj) {
|
MasterKey.isMasterKey = function isMasterKey(obj) {
|
||||||
return obj
|
return obj
|
||||||
&& typeof obj.encrypted === 'boolean'
|
&& typeof obj.encrypted === 'boolean'
|
||||||
|
|||||||
@ -505,6 +505,31 @@ WalletDB.prototype.getAccount = function getAccount(id, name, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List account names and indexes from the db.
|
||||||
|
* @param {WalletID} id
|
||||||
|
* @param {Function} callback - Returns [Error, Array].
|
||||||
|
*/
|
||||||
|
|
||||||
|
WalletDB.prototype.getAccounts = function getAccounts(id, callback) {
|
||||||
|
var accounts = [];
|
||||||
|
this.db.iterate({
|
||||||
|
gte: 'i/' + id + '/',
|
||||||
|
lte: 'i/' + id + '/~',
|
||||||
|
values: true,
|
||||||
|
parse: function(value, key) {
|
||||||
|
var name = key.split('/')[2];
|
||||||
|
var index = value.readUInt32LE(0, true);
|
||||||
|
accounts[index] = name;
|
||||||
|
}
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
return callback(null, accounts);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup the corresponding account name's index.
|
* Lookup the corresponding account name's index.
|
||||||
* @param {WalletID} id
|
* @param {WalletID} id
|
||||||
|
|||||||
@ -768,7 +768,11 @@ describe('Wallet', function() {
|
|||||||
w1.fill(t3, { rate: 10000, round: true }, function(err) {
|
w1.fill(t3, { rate: 10000, round: true }, function(err) {
|
||||||
assert(err);
|
assert(err);
|
||||||
assert.equal(err.requiredFunds, 25000);
|
assert.equal(err.requiredFunds, 25000);
|
||||||
cb();
|
w1.getAccounts(function(err, accounts) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.deepEqual(accounts, ['default', 'foo']);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -843,6 +847,43 @@ describe('Wallet', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fill tx with inputs when encrypted', function(cb) {
|
||||||
|
wdb.create({ passphrase: 'foo' }, function(err, w1) {
|
||||||
|
assert.ifError(err);
|
||||||
|
w1.master.destroy();
|
||||||
|
|
||||||
|
// Coinbase
|
||||||
|
var t1 = bcoin.mtx()
|
||||||
|
.addOutput(w1, 5460)
|
||||||
|
.addOutput(w1, 5460)
|
||||||
|
.addOutput(w1, 5460)
|
||||||
|
.addOutput(w1, 5460);
|
||||||
|
|
||||||
|
t1.addInput(dummyInput);
|
||||||
|
|
||||||
|
wdb.addTX(t1, function(err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
// Create new transaction
|
||||||
|
var t2 = bcoin.mtx().addOutput(w1, 5460);
|
||||||
|
w1.fill(t2, { rate: 10000, round: true }, function(err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
// Should fail
|
||||||
|
w1.sign(t2, 'bar', function(err) {
|
||||||
|
assert(err);
|
||||||
|
assert(!t2.verify());
|
||||||
|
// Should succeed
|
||||||
|
w1.sign(t2, 'foo', function(err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert(t2.verify());
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should cleanup', function(cb) {
|
it('should cleanup', function(cb) {
|
||||||
constants.tx.COINBASE_MATURITY = 100;
|
constants.tx.COINBASE_MATURITY = 100;
|
||||||
cb();
|
cb();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user