make encryption async.

This commit is contained in:
Christopher Jeffrey 2016-06-24 19:07:07 -07:00
parent 4c9dca65bd
commit f784c25d01
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 468 additions and 336 deletions

View File

@ -453,7 +453,7 @@ function normalize(path, dirname) {
*/
function mkdirp(path) {
var i, parts;
var i, parts, stat;
if (!fs)
return;
@ -477,7 +477,9 @@ function mkdirp(path) {
path += parts[i];
try {
fs.statSync(path);
stat = fs.statSync(path);
if (!stat.isDirectory())
throw new Error('Could not create directory.');
} catch (e) {
if (e.code === 'ENOENT')
fs.mkdirSync(path, 488 /* 0750 */);

View File

@ -144,8 +144,7 @@ Mnemonic.prototype.toSeed = function toSeed() {
this.seed = utils.pbkdf2(
nfkd(this.phrase),
nfkd('mnemonic' + this.passphrase),
2048,
64);
2048, 64, 'sha512');
return this.seed;
};
@ -304,12 +303,13 @@ HD.fromMnemonic = function fromMnemonic(options, network) {
/**
* Instantiate an HD key from a jsonified key object.
* @param {Object} json - The jsonified transaction object.
* @param {String?} passphrase
* @returns {HDPrivateKey|HDPublicKey}
*/
HD.fromJSON = function fromJSON(json, passphrase) {
return HDPrivateKey.fromJSON(json, passphrase);
HD.fromJSON = function fromJSON(json) {
if (json.xprivkey)
return HDPrivateKey.fromJSON(json);
return HDPublicKey.fromJSON(json);
};
/**
@ -995,29 +995,20 @@ HDPrivateKey.fromRaw = function fromRaw(raw) {
/**
* Convert key to a more json-friendly object.
* @param {String?} passphrase - Address passphrase
* @returns {Object}
*/
HDPrivateKey.prototype.toJSON = function toJSON(passphrase) {
HDPrivateKey.prototype.toJSON = function toJSON() {
var json = {
network: this.network,
encrypted: false
network: this.network
};
if (this instanceof HDPrivateKey) {
json.encrypted = passphrase ? true : false;
if (this.mnemonic) {
json.phrase = passphrase
? utils.encrypt(this.mnemonic.phrase, passphrase).toString('hex')
: this.mnemonic.phrase;
json.passphrase = passphrase
? utils.encrypt(this.mnemonic.passphrase, passphrase).toString('hex')
: this.mnemonic.passphrase;
json.phrase = this.mnemonic.phrase;
json.passphrase = this.mnemonic.passphrase;
}
json.xprivkey = passphrase
? utils.encrypt(this.xprivkey, passphrase).toString('hex')
: this.xprivkey;
json.xprivkey = this.xprivkey;
return json;
}
@ -1032,66 +1023,35 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) {
* @returns {Object} A "naked" HDPrivateKey.
*/
HDPrivateKey.parseJSON = function parseJSON(json, passphrase) {
HDPrivateKey.parseJSON = function parseJSON(json) {
var data = {};
if (json.encrypted && !passphrase)
throw new Error('Cannot decrypt address');
if (json.phrase) {
data.mnemonic = {
phrase: json.encrypted
? utils.decrypt(json.phrase, passphrase).toString('utf8')
: json.phrase,
passphrase: json.encrypted
? utils.decrypt(json.passphrase, passphrase).toString('utf8')
: json.passphrase
};
if (!json.xprivkey)
return data;
}
if (json.xprivkey) {
data.xprivkey = json.encrypted
? utils.decrypt(json.xprivkey, passphrase).toString('utf8')
: json.xprivkey;
return data;
}
if (json.xpubkey) {
return {
xpubkey: json.xpubkey
phrase: json.phrase,
passphrase: json.passphrase
};
}
assert(false);
assert(json.xprivkey, 'Could not handle key JSON.');
data.xprivkey = json.xprivkey;
return data;
};
/**
* Instantiate an HDPrivateKey from a jsonified key object.
* @param {Object} json - The jsonified transaction object.
* @param {String?} passphrase
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromJSON = function fromJSON(json, passphrase) {
HDPrivateKey.fromJSON = function fromJSON(json) {
var key;
json = HDPrivateKey.parseJSON(json, passphrase);
if (json.xprivkey) {
key = HDPrivateKey.fromBase58(json.xprivkey);
key.mnemonic = json.mnemonic ? new Mnemonic(json.mnemonic) : null;
return key;
}
if (json.mnemonic)
return HDPrivateKey.fromMnemonic(json.mnemonic, json.network);
if (json.xpubkey)
return HDPublicKey.fromBase58(json.xprivkey);
assert(false, 'Could not handle HD key JSON.');
json = HDPrivateKey.parseJSON(json);
key = HDPrivateKey.fromBase58(json.xprivkey);
key.mnemonic = json.mnemonic ? new Mnemonic(json.mnemonic) : null;
return key;
};
/**
@ -1330,7 +1290,13 @@ HDPublicKey.prototype.equal = function equal(obj) {
* @returns {Object}
*/
HDPublicKey.prototype.toJSON = HDPrivateKey.prototype.toJSON;
HDPublicKey.prototype.toJSON = function toJSON() {
return {
network: this.network,
xpubkey: this.xpubkey
};
};
/**
* Handle a deserialized JSON HDPublicKey object.
@ -1338,7 +1304,12 @@ HDPublicKey.prototype.toJSON = HDPrivateKey.prototype.toJSON;
* @returns {Object} A "naked" HDPublicKey.
*/
HDPublicKey.parseJSON = HDPrivateKey.parseJSON;
HDPublicKey.parseJSON = function parseJSON(json) {
assert(json.xpubkey, 'Could not handle HD key JSON.');
return {
xpubkey: json.xpubkey
};
};
/**
* Instantiate an HDPrivateKey from a jsonified key object.
@ -1347,7 +1318,10 @@ HDPublicKey.parseJSON = HDPrivateKey.parseJSON;
* @returns {HDPrivateKey}
*/
HDPublicKey.fromJSON = HDPrivateKey.fromJSON;
HDPublicKey.fromJSON = function fromJSON(json) {
json = HDPrivateKey.parseJSON(json);
return HDPublicKey.fromBase58(json.xpubkey);
};
/**
* Test whether an object is in the form of a base58 xpubkey.

View File

@ -352,21 +352,37 @@ utils.hmac = function hmac(alg, data, salt) {
* Perform key stretching using PBKDF2.
* @param {Buffer} key
* @param {Buffer} salt
* @param {Number} iterations
* @param {Number} dkLen - Output size.
* @param {String?} alg
* @param {Number} iter
* @param {Number} len
* @param {String} alg
* @returns {Buffer}
*/
/*!
* PBKDF2
* Credit to: https://github.com/stayradiated/pbkdf2-sha512
* Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved
* Copyright (c) 2014, JP Richardson
utils.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) {
if (typeof key === 'string')
key = new Buffer(key, 'utf8');
if (typeof salt === 'string')
salt = new Buffer(salt, 'utf8');
if (crypto && crypto.pbkdf2Sync)
return crypto.pbkdf2Sync(key, salt, iter, len, alg);
return utils._pbkdf2(key, salt, iter, len, alg);
};
/**
* Execute pbkdf2 asynchronously.
* @param {Buffer} key
* @param {Buffer} salt
* @param {Number} iter
* @param {Number} len
* @param {String} alg
* @param {Function} callback
*/
utils.pbkdf2 = function pbkdf2(key, salt, iterations, dkLen, alg) {
var hLen, DK, U, T, block, l, r;
var i, j, k, destPos, len;
utils.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg, callback) {
var result;
if (typeof key === 'string')
key = new Buffer(key, 'utf8');
@ -374,154 +390,166 @@ utils.pbkdf2 = function pbkdf2(key, salt, iterations, dkLen, alg) {
if (typeof salt === 'string')
salt = new Buffer(salt, 'utf8');
if (!alg)
alg = 'sha512';
if (crypto && crypto.pbkdf2)
return crypto.pbkdf2(key, salt, iter, len, alg, callback);
if (crypto && crypto.pbkdf2Sync)
return crypto.pbkdf2Sync(key, salt, iterations, dkLen, alg);
if (alg === 'sha512')
hLen = 64;
else if (alg === 'sha256')
hLen = 32;
else if (alg === 'sha1' || alg === 'ripemd160')
hLen = 20;
else if (alg === 'md5')
hLen = 16;
assert(dkLen <= 0xffffffff * hLen, 'Requested key length too long');
DK = new Buffer(dkLen);
T = new Buffer(hLen);
block = new Buffer(salt.length + 4);
l = Math.ceil(dkLen / hLen);
r = dkLen - (l - 1) * hLen;
salt.copy(block, 0, 0, salt.length);
for (i = 1; i <= l; i++) {
block[salt.length + 0] = (i >>> 24) & 0xff;
block[salt.length + 1] = (i >>> 16) & 0xff;
block[salt.length + 2] = (i >>> 8) & 0xff;
block[salt.length + 3] = (i >>> 0) & 0xff;
U = utils.hmac(alg, block, key);
U.copy(T, 0, 0, hLen);
for (j = 1; j < iterations; j++) {
U = utils.hmac(alg, U, key);
for (k = 0; k < hLen; k++)
T[k] ^= U[k];
}
destPos = (i - 1) * hLen;
len = i === l ? r : hLen;
T.copy(DK, destPos, 0, len);
try {
result = utils._pbkdf2(key, salt, iter, len, alg);
} catch (e) {
return callback(e);
}
return DK;
return utils.asyncify(callback)(null, result);
};
/**
* Derive a key using pbkdf2 with 25,000 iterations.
* @param {Buffer|String} passphrase
* @param {Function} callback
*/
utils.derive = function derive(passphrase, callback) {
utils.pbkdf2Async(passphrase, 'bcoin', 25000, 32, 'sha256', callback);
};
/**
* Encrypt with aes-256-cbc. Derives key with {@link utils.derive}.
* @param {Buffer} data
* @param {Buffer|String} passphrase
* @param {Buffer} iv - 128 bit initialization vector.
* @param {Function} callback
*/
utils.encrypt = function encrypt(data, passphrase, iv, callback) {
assert(Buffer.isBuffer(data));
assert(passphrase, 'No passphrase.');
utils.derive(passphrase, function(err, key) {
if (err)
return callback(err);
try {
data = utils.encipher(data, key, iv);
} catch (e) {
key.fill(0);
return callback(e);
}
key.fill(0);
return callback(null, data);
});
};
/**
* Encrypt with aes-256-cbc.
* @param {Buffer|String} data
* @param {Buffer|String} passphrase
* @param {Buffer} data
* @param {Buffer} key - 256 bit key.
* @param {Buffer} iv - 128 bit initialization vector.
* @returns {Buffer}
*/
utils.encrypt = function encrypt(data, passphrase) {
var key, cipher, out;
utils.encipher = function encipher(data, key, iv) {
var cipher;
assert(passphrase, 'No passphrase.');
if (!crypto)
return aes.cbc.encrypt(data, key, iv);
if (typeof data === 'string')
data = new Buffer(data, 'utf8');
cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
if (typeof passphrase === 'string')
passphrase = new Buffer(passphrase, 'utf8');
key = utils.pbkdf2key(passphrase, 2048, 32, 16);
if (!crypto) {
out = aes.cbc.encrypt(data, key.key, key.iv);
key.key.fill(0);
key.iv.fill(0);
return out;
}
cipher = crypto.createCipheriv('aes-256-cbc', key.key, key.iv);
out = Buffer.concat([
return Buffer.concat([
cipher.update(data),
cipher.final()
]);
key.key.fill(0);
key.iv.fill(0);
return out;
};
/**
* Decrypt from aes-256-cbc.
* @param {Buffer|String} data
* Decrypt with aes-256-cbc. Derives key with {@link utils.derive}.
* @param {Buffer} data
* @param {Buffer|String} passphrase
* @param {Buffer} iv - 128 bit initialization vector.
* @param {Function} callback
*/
utils.decrypt = function decrypt(data, passphrase, iv, callback) {
assert(Buffer.isBuffer(data));
assert(passphrase, 'No passphrase.');
utils.derive(passphrase, function(err, key) {
if (err)
return callback(err);
try {
data = utils.decipher(data, key, iv);
} catch (e) {
key.fill(0);
return callback(e);
}
key.fill(0);
return callback(null, data);
});
};
/**
* Decrypt with aes-256-cbc.
* @param {Buffer} data
* @param {Buffer} key - 256 bit key.
* @param {Buffer} iv - 128 bit initialization vector.
* @returns {Buffer}
*/
utils.decrypt = function decrypt(data, passphrase) {
var key, decipher, out;
utils.decipher = function decipher(data, key, iv) {
var key, decipher;
assert(passphrase, 'No passphrase.');
if (!crypto)
return aes.cbc.decrypt(data, key, iv);
if (typeof data === 'string')
data = new Buffer(data, 'hex');
decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
if (typeof passphrase === 'string')
passphrase = new Buffer(passphrase, 'utf8');
key = utils.pbkdf2key(passphrase, 2048, 32, 16);
if (!crypto) {
out = aes.cbc.decrypt(data, key.key, key.iv);
key.key.fill(0);
key.iv.fill(0);
return out;
}
decipher = crypto.createDecipheriv('aes-256-cbc', key.key, key.iv);
out = Buffer.concat([
return Buffer.concat([
decipher.update(data),
decipher.final()
]);
key.key.fill(0);
key.iv.fill(0);
return out;
};
/**
* Generate a key and IV using pbkdf2.
* @param {Buffer|String} passphrase
* @param {(Buffer|String)?} salt
* @param {Number} iterations
* @param {Number} dkLen
* @param {Number} ivLen
* @param {String?} alg
* Perform key stretching using PBKDF2.
* @private
* @param {Buffer} key
* @param {Buffer} salt
* @param {Number} iter
* @param {Number} len
* @param {String} alg
* @returns {Buffer}
*/
utils.pbkdf2key = function pbkdf2key(passphrase, iterations, dkLen, ivLen, alg) {
var key = utils.pbkdf2(passphrase, '', iterations, dkLen + ivLen, alg);
return {
key: key.slice(0, dkLen),
iv: key.slice(dkLen, dkLen + ivLen)
};
utils._pbkdf2 = function pbkdf2(key, salt, iter, len, alg) {
var size = utils.hash(alg, '').length;
var blocks = Math.ceil(len / size);
var out = new Buffer(blocks * size);
var buf = new Buffer(salt.length + 4);
var block = new Buffer(size);
var pos = 0;
var i, j, k, mac;
salt.copy(buf, 0);
for (i = 0; i < blocks; i++) {
buf.writeUInt32BE(i + 1, salt.length, true);
mac = utils.hmac(alg, buf, key);
mac.copy(block, 0);
for (j = 1; j < iter; j++) {
mac = utils.hmac(alg, mac, key);
for (k = 0; k < size; k++)
block[k] ^= mac[k];
}
block.copy(out, pos);
pos += size;
}
return out.slice(0, len);
};
/**

View File

@ -94,18 +94,20 @@ Wallet.prototype.init = function init(options, callback) {
assert(!this.initialized);
this.initialized = true;
if (options.passphrase)
this.master.encrypt(options.passphrase);
this.createAccount(options, function(err, account) {
this.master.encrypt(options.passphrase, function(err) {
if (err)
return callback(err);
assert(account);
self.createAccount(options, function(err, account) {
if (err)
return callback(err);
self.account = account;
assert(account);
return callback();
self.account = account;
return callback();
});
});
};
@ -135,10 +137,9 @@ Wallet.prototype.open = function open(callback) {
Wallet.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
this.master.destroy();
try {
this.db.unregister(this);
if (this.db.unregister(this))
this.master.destroy();
} catch (e) {
this.emit('error', e);
return callback(e);
@ -231,32 +232,35 @@ Wallet.prototype.removeKey = function removeKey(account, key, callback) {
*/
Wallet.prototype.setPassphrase = function setPassphrase(old, new_, callback) {
var self = this;
var unlock;
if (typeof new_ === 'function') {
callback = new_;
new_ = old;
old = null;
}
if (old) {
try {
this.master.decrypt(old);
} catch (e) {
return callback(e);
}
}
unlock = this.locker.lock(setPassphrase, [old, new_, callback]);
if (new_) {
try {
this.master.encrypt(new_);
} catch (e) {
return callback(e);
}
}
if (!unlock)
return;
return this.save(callback);
callback = utils.wrap(callback, unlock);
this.master.decrypt(old, function(err) {
if (err)
return callback(err);
self.master.encrypt(new_, function(err) {
if (err)
return callback(err);
return self.save(callback);
});
});
};
/**
* Lock the wallet, destroy decrypted key.
*/
@ -271,8 +275,8 @@ Wallet.prototype.lock = function lock() {
* @param {Number?} [timeout=60000] - ms.
*/
Wallet.prototype.unlock = function unlock(passphrase, timeout) {
this.master.toKey(passphrase, timeout);
Wallet.prototype.unlock = function unlock(passphrase, timeout, callback) {
this.master.toKey(passphrase, timeout, callback);
};
/**
@ -308,7 +312,7 @@ Wallet.prototype.getID = function getID() {
Wallet.prototype.createAccount = function createAccount(options, callback, force) {
var self = this;
var master, key, unlock;
var key, unlock;
unlock = this.locker.lock(createAccount, [options, callback], force);
@ -317,37 +321,36 @@ Wallet.prototype.createAccount = function createAccount(options, callback, force
callback = utils.wrap(callback, unlock);
try {
master = this.master.toKey(options.passphrase, options.timeout);
} catch (e) {
return callback(e);
}
key = master.deriveAccount44(this.accountDepth);
options = {
network: this.network,
id: this.id,
name: this.accountDepth === 0 ? 'default' : options.name,
witness: options.witness,
accountKey: key.hdPublicKey,
accountIndex: this.accountDepth,
type: options.type,
keys: options.keys,
m: options.m,
n: options.n
};
this.db.createAccount(options, function(err, account) {
this.master.toKey(options.passphrase, options.timeout, function(err, master) {
if (err)
return callback(err);
self.accountDepth++;
key = master.deriveAccount44(self.accountDepth);
self.save(function(err) {
options = {
network: self.network,
id: self.id,
name: self.accountDepth === 0 ? 'default' : options.name,
witness: options.witness,
accountKey: key.hdPublicKey,
accountIndex: self.accountDepth,
type: options.type,
keys: options.keys,
m: options.m,
n: options.n
};
self.db.createAccount(options, function(err, account) {
if (err)
return callback(err);
return callback(null, account);
self.accountDepth++;
self.save(function(err) {
if (err)
return callback(err);
return callback(null, account);
});
});
});
};
@ -976,7 +979,7 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, callback) {
Wallet.prototype.sign = function sign(tx, options, callback) {
var self = this;
var total = 0;
var i, address, key, master;
var i, address, key;
if (typeof options === 'function') {
callback = options;
@ -986,25 +989,24 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
if (typeof options === 'string' || Buffer.isBuffer(options))
options = { passphrase: options };
this.deriveInputs(tx, function(err, addresses) {
this.master.toKey(options.passphrase, options.timeout, function(err, master) {
if (err)
return callback(err);
try {
master = self.master.toKey(options.passphrase, options.timeout);
} catch (e) {
return callback(e);
}
self.deriveInputs(tx, function(err, addresses) {
if (err)
return callback(err);
for (i = 0; i < addresses.length; i++) {
address = addresses[i];
key = master.deriveAccount44(address.account);
key = key.derive(address.change).derive(address.index);
assert(utils.equal(key.getPublicKey(), address.key));
total += address.sign(tx, key, options.index, options.type);
}
for (i = 0; i < addresses.length; i++) {
address = addresses[i];
key = master.deriveAccount44(address.account);
key = key.derive(address.change).derive(address.index);
assert(utils.equal(key.getPublicKey(), address.key));
total += address.sign(tx, key, options.index, options.type);
}
return callback(null, total);
return callback(null, total);
});
});
};
@ -1532,7 +1534,6 @@ function Account(db, options) {
this.db = db;
this.network = db.network;
this.lookahead = Account.LOOKAHEAD;
this.cache = new bcoin.lru(20, 1);
this.loaded = false;
this.loading = false;
@ -2276,7 +2277,7 @@ Account.isAccount = function isAccount(obj) {
/**
* Master BIP32 key which can exist
* in an timed out encrypted state.
* in a timed out encrypted state.
* @exports Master
* @constructor
* @param {Object} options
@ -2291,15 +2292,26 @@ function MasterKey(options) {
this.phrase = null;
this.passphrase = null;
this.key = null;
this.iv = null;
this.ciphertext = null;
this.timer = null;
this._destroy = this.destroy.bind(this);
this.locker = new bcoin.locker(this);
}
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
MasterKey.prototype.fromOptions = function fromOptions(options) {
this.encrypted = !!options.encrypted;
this.xprivkey = options.xprivkey;
this.phrase = options.phrase;
this.passphrase = options.passphrase;
this.iv = options.iv;
this.ciphertext = options.ciphertext;
this.key = options.key || null;
assert(this.encrypted ? !this.key : this.key);
@ -2307,6 +2319,11 @@ MasterKey.prototype.fromOptions = function fromOptions(options) {
return this;
};
/**
* Instantiate master key from options.
* @returns {MasterKey}
*/
MasterKey.fromOptions = function fromOptions(options) {
return new MasterKey().fromOptions(options);
};
@ -2318,18 +2335,42 @@ MasterKey.fromOptions = function fromOptions(options) {
* @returns {HDPrivateKey}
*/
MasterKey.prototype.toKey = function toKey(passphrase, timeout) {
var xprivkey;
MasterKey.prototype.toKey = function toKey(passphrase, timeout, callback) {
var self = this;
var unlock, data;
if (!this.key) {
assert(this.encrypted);
xprivkey = utils.decrypt(this.xprivkey, passphrase);
this.key = bcoin.hd.fromRaw(xprivkey);
xprivkey.fill(0);
this.start(timeout);
}
unlock = this.locker.lock(toKey, [passphrase, timeout, callback]);
return this.key;
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
if (this.key)
return callback(null, this.key);
if (!passphrase)
return callback(new Error('No passphrase.'));
assert(this.encrypted);
utils.decrypt(this.ciphertext, passphrase, this.iv, function(err, data) {
if (err)
return callback(err);
try {
data = MasterKey.fromRaw(data);
} catch (e) {
return callback(e);
}
self.key = data.key;
self.start(timeout);
data.destroy();
return callback(null, self.key);
});
};
/**
@ -2390,25 +2431,39 @@ MasterKey.prototype.destroy = function destroy() {
* @param {Buffer|String} passphrase - Zero this yourself.
*/
MasterKey.prototype.decrypt = function decrypt(passphrase) {
MasterKey.prototype.decrypt = function decrypt(passphrase, callback) {
var self = this;
var unlock;
unlock = this.locker.lock(decrypt, [passphrase, callback]);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
if (!this.encrypted) {
assert(this.key);
return;
return callback();
}
assert(passphrase, 'Passphrase is required.');
if (!passphrase)
return callback();
this.destroy();
this.encrypted = false;
this.xprivkey = utils.decrypt(this.xprivkey, passphrase);
utils.decrypt(this.ciphertext, passphrase, this.iv, function(err, data) {
if (err)
return callback(err);
if (this.phrase) {
this.phrase = utils.decrypt(this.phrase, passphrase);
this.passphrase = utils.decrypt(this.passphrase, passphrase);
}
self.encrypted = false;
self.iv = null;
self.ciphertext = null;
this.key = bcoin.hd.fromRaw(this.xprivkey);
self.fromRaw(data);
return callback();
});
};
/**
@ -2416,39 +2471,70 @@ MasterKey.prototype.decrypt = function decrypt(passphrase) {
* @param {Buffer|String} passphrase - Zero this yourself.
*/
MasterKey.prototype.encrypt = function encrypt(passphrase) {
var xprivkey = this.xprivkey;
var phrase = this.phrase;
var pass = this.passphrase;
MasterKey.prototype.encrypt = function encrypt(passphrase, callback) {
var self = this;
var unlock, data, iv;
unlock = this.locker.lock(encrypt, [passphrase, callback]);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
if (this.encrypted)
return;
assert(passphrase, 'Passphrase is required.');
if (!passphrase)
return callback();
this.key = null;
this.encrypted = true;
this.xprivkey = utils.encrypt(xprivkey, passphrase);
xprivkey.fill(0);
iv = bcoin.ec.random(16);
data = this.toRaw();
if (this.phrase) {
this.phrase = utils.encrypt(phrase, passphrase);
this.passphrase = utils.encrypt(pass, passphrase);
phrase.fill(0);
pass.fill(0);
}
utils.encrypt(data, passphrase, iv, function(err, data) {
if (err)
return callback(err);
self.key = null;
self.encrypted = true;
self.iv = iv;
self.ciphertext = data;
self.xprivkey.fill(0);
self.xprivkey = null;
if (self.phrase) {
self.phrase.fill(0);
self.phrase = null;
self.passphrase.fill(0);
self.passphrase = null;
}
return callback();
});
};
/**
* Serialize the key in the form of:
* `[enc-flag][phrase-marker][phrase?][passphrase?][xprivkey]`
* `[enc-flag][iv?][ciphertext?][phrase-marker][phrase?][passphrase?][xprivkey]`
* @returns {Buffer}
*/
MasterKey.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
p.writeU8(this.encrypted ? 1 : 0);
if (this.encrypted) {
p.writeU8(1);
p.writeVarBytes(this.iv);
p.writeVarBytes(this.ciphertext);
if (!writer)
p = p.render();
return p;
}
p.writeU8(0);
if (this.phrase) {
p.writeU8(1);
@ -2458,7 +2544,7 @@ MasterKey.prototype.toRaw = function toRaw(writer) {
p.writeU8(0);
}
p.writeVarBytes(this.xprivkey);
p.writeBytes(this.xprivkey);
if (!writer)
p = p.render();
@ -2467,8 +2553,9 @@ MasterKey.prototype.toRaw = function toRaw(writer) {
};
/**
* Instantiate master key from serialized data.
* @returns {MasterKey}
* Inject properties from serialized data.
* @private
* @param {Buffer} raw
*/
MasterKey.prototype.fromRaw = function fromRaw(raw) {
@ -2476,31 +2563,42 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) {
this.encrypted = p.readU8() === 1;
if (this.encrypted) {
this.iv = p.readVarBytes();
this.ciphertext = p.readVarBytes();
return this;
}
if (p.readU8() === 1) {
this.phrase = p.readVarBytes();
this.passphrase = p.readVarBytes();
}
this.xprivkey = p.readVarBytes();
if (!this.encrypted)
this.key = bcoin.hd.fromRaw(this.xprivkey);
this.xprivkey = p.readBytes(82);
this.key = bcoin.hd.fromRaw(this.xprivkey);
return this;
};
/**
* Instantiate master key from serialized data.
* @returns {MasterKey}
*/
MasterKey.fromRaw = function fromRaw(raw) {
return new MasterKey().fromRaw(raw);
};
/**
* Instantiate master key from an HDPrivateKey.
* Inject properties from an HDPrivateKey.
* @private
* @param {HDPrivateKey} key
* @returns {MasterKey}
*/
MasterKey.prototype.fromKey = function fromKey(key) {
this.encrypted = false;
this.iv = null;
this.ciphertext = null;
if (key.mnemonic) {
this.phrase = new Buffer(key.mnemonic.phrase, 'utf8');
@ -2513,6 +2611,12 @@ MasterKey.prototype.fromKey = function fromKey(key) {
return this;
};
/**
* Instantiate master key from an HDPrivateKey.
* @param {HDPrivateKey} key
* @returns {MasterKey}
*/
MasterKey.fromKey = function fromKey(key) {
return new MasterKey().fromKey(key);
};
@ -2523,14 +2627,11 @@ MasterKey.fromKey = function fromKey(key) {
*/
MasterKey.prototype.toJSON = function toJSON() {
var phrase, passphrase, xprivkey;
var phrase, passphrase, xprivkey, iv, ciphertext;
if (this.encrypted) {
if (this.phrase) {
phrase = this.phrase.toString('hex');
passphrase = this.passphrase.toString('hex');
}
xprivkey = this.xprivkey.toString('hex');
iv = this.iv.toString('hex');
ciphertext = this.ciphertext.toString('hex');
} else {
if (this.phrase) {
phrase = this.phrase.toString('utf8');
@ -2541,6 +2642,8 @@ MasterKey.prototype.toJSON = function toJSON() {
return {
encrypted: this.encrypted,
iv: iv,
ciphertext: ciphertext,
phrase: phrase,
passphrase: passphrase,
xprivkey: xprivkey
@ -2548,35 +2651,46 @@ MasterKey.prototype.toJSON = function toJSON() {
};
/**
* Instantiate master key from jsonified object.
* @returns {MasterKey}
* Inject properties from JSON object.
* @private
* @param {Object} json
*/
MasterKey.prototype.fromJSON = function fromJSON(json) {
this.encrypted = json.encrypted;
if (json.encrypted) {
if (json.phrase) {
this.phrase = new Buffer(json.phrase, 'hex');
this.passphrase = new Buffer(json.passphrase, 'hex');
}
this.xprivkey = new Buffer(json.xprivkey, 'hex');
this.iv = new Buffer(json.iv, 'hex');
this.ciphertext = new Buffer(json.ciphertext, 'hex');
} else {
if (json.phrase) {
this.phrase = new Buffer(json.phrase, 'utf8');
this.passphrase = new Buffer(json.passphrase, 'utf8');
}
this.xprivkey = utils.fromBase58(json.xprivkey);
this.key = bcoin.hd.fromRaw(this.xprivkey);
}
if (!json.encrypted)
this.key = bcoin.hd.fromRaw(xprivkey);
return this;
};
MasterKey.fromJSON = function fromJSON(key) {
return new MasterKey().fromJSON(key);
/**
* Instantiate master key from jsonified object.
* @param {Object} json
* @returns {MasterKey}
*/
MasterKey.fromJSON = function fromJSON(json) {
return new MasterKey().fromJSON(json);
};
/**
* Inspect the key.
* @returns {Object}
*/
MasterKey.prototype.inspect = function inspect() {
return this.toJSON();
};
/**

View File

@ -271,6 +271,7 @@ WalletDB.prototype.register = function register(object) {
/**
* Unregister a object with the walletdb.
* @param {Object} object
* @returns {Boolean}
*/
WalletDB.prototype.unregister = function unregister(object) {
@ -278,13 +279,17 @@ WalletDB.prototype.unregister = function unregister(object) {
var watcher = this.watchers[id];
if (!watcher)
return;
return false;
assert(watcher.object === object);
assert(watcher.refs !== 0, '`destroy()` called twice!');
if (--watcher.refs === 0)
if (--watcher.refs === 0) {
delete this.watchers[id];
return true;
}
return false;
};
/**
@ -1049,7 +1054,7 @@ WalletDB.prototype.fetchWallet = function fetchWallet(id, callback, handler) {
handler(wallet, function(err, result) {
// Kill the reference.
self.unregister(wallet);
wallet.destroy();
if (err)
return callback(err);

View File

@ -7,6 +7,14 @@ var aes = require('../lib/bcoin/aes');
var crypto = require('crypto');
describe('AES', function() {
function pbkdf2key(passphrase, iterations, dkLen, ivLen, alg) {
var key = utils.pbkdf2(passphrase, '', iterations, dkLen + ivLen, 'sha512');
return {
key: key.slice(0, dkLen),
iv: key.slice(dkLen, dkLen + ivLen)
};
}
function nencrypt(data, passphrase) {
var key, cipher;
@ -19,7 +27,7 @@ describe('AES', function() {
if (typeof passphrase === 'string')
passphrase = new Buffer(passphrase, 'utf8');
key = utils.pbkdf2key(passphrase, 2048, 32, 16);
key = pbkdf2key(passphrase, 2048, 32, 16);
cipher = crypto.createCipheriv('aes-256-cbc', key.key, key.iv);
return Buffer.concat([
@ -40,7 +48,7 @@ describe('AES', function() {
if (typeof passphrase === 'string')
passphrase = new Buffer(passphrase, 'utf8');
key = utils.pbkdf2key(passphrase, 2048, 32, 16);
key = pbkdf2key(passphrase, 2048, 32, 16);
decipher = crypto.createDecipheriv('aes-256-cbc', key.key, key.iv);
return Buffer.concat([
@ -61,7 +69,7 @@ describe('AES', function() {
if (typeof passphrase === 'string')
passphrase = new Buffer(passphrase, 'utf8');
key = utils.pbkdf2key(passphrase, 2048, 32, 16);
key = pbkdf2key(passphrase, 2048, 32, 16);
return aes.cbc.encrypt(data, key.key, key.iv);
}
@ -78,7 +86,7 @@ describe('AES', function() {
if (typeof passphrase === 'string')
passphrase = new Buffer(passphrase, 'utf8');
key = utils.pbkdf2key(passphrase, 2048, 32, 16);
key = pbkdf2key(passphrase, 2048, 32, 16);
return aes.cbc.decrypt(data, key.key, key.iv);
}

View File

@ -90,7 +90,7 @@ describe('HD', function() {
var master, child1, child2, child3, child4, child5, child6;
it('should create a pbkdf2 seed', function() {
var checkSeed = bcoin.utils.pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64).toString('hex');
var checkSeed = bcoin.utils.pbkdf2(phrase, 'mnemonic' + 'foo', 2048, 64, 'sha512').toString('hex');
assert.equal(checkSeed, seed);
});

View File

@ -869,7 +869,8 @@ describe('Wallet', function() {
it('should fill tx with inputs when encrypted', function(cb) {
walletdb.create({ passphrase: 'foo' }, function(err, w1) {
assert.ifError(err);
w1.master.destroy();
w1.master.stop();
w1.master.key = null;
// Coinbase
var t1 = bcoin.mtx()