From 1a93f84b47435125a6d6d1aa9c112c349fb0facd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 2 Oct 2016 22:44:32 -0700 Subject: [PATCH] wallet: support scrypt as a derivation method. --- lib/wallet/account.js | 2 +- lib/wallet/wallet.js | 134 +++++++++++++++++++++++++++++++++--------- 2 files changed, 108 insertions(+), 28 deletions(-) diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 688b12b5..caeb9dac 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -528,7 +528,7 @@ Account.prototype.derivePath = function derivePath(path, master) { /** * Derive an address at `index`. Do not increment depth. - * @param {Boolean} branch - Whether the address on the change branch. + * @param {Number} branch - Whether the address on the change branch. * @param {Number} index * @returns {WalletKey} */ diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 255ae50c..ff48725f 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -2198,13 +2198,43 @@ function MasterKey(options) { this.ciphertext = null; this.key = null; + this.alg = MasterKey.alg.PBKDF2; + this.N = 50000; + this.r = 0; + this.p = 0; + this.aesKey = null; this.timer = null; this.until = 0; this._destroy = this.destroy.bind(this); this.locker = new Locker(this); + + if (options) + this.fromOptions(options); } +/** + * Key derivation algorithms. + * @enum {Number} + * @default + */ + +MasterKey.alg = { + PBKDF2: 0, + SCRYPT: 1 +}; + +/** + * Key derivation algorithms by value. + * @enum {String} + * @default + */ + +MasterKey.algByVal = { + 0: 'pbkdf2', + 1: 'scrypt' +}; + /** * Inject properties from options object. * @private @@ -2212,6 +2242,8 @@ function MasterKey(options) { */ MasterKey.prototype.fromOptions = function fromOptions(options) { + var alg; + assert(options); if (options.encrypted != null) { @@ -2234,6 +2266,37 @@ MasterKey.prototype.fromOptions = function fromOptions(options) { this.key = options.key; } + if (options.alg != null) { + if (typeof options.alg === 'string') { + this.alg = MasterKey.alg[options.alg.toLowerCase()]; + assert(this.alg != null, 'Unknown algorithm.'); + } else { + assert(typeof options.alg === 'number'); + assert(MasterKey.algByVal[options.alg]); + this.alg = options.alg; + } + } + + if (options.rounds != null) { + assert(utils.isNumber(options.rounds)); + this.N = options.rounds; + } + + if (options.N != null) { + assert(utils.isNumber(options.N)); + this.N = options.N; + } + + if (options.r != null) { + assert(utils.isNumber(options.r)); + this.r = options.r; + } + + if (options.p != null) { + assert(utils.isNumber(options.p)); + this.p = options.p; + } + assert(this.encrypted ? !this.key : this.key); return this; @@ -2283,7 +2346,7 @@ MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) { assert(this.encrypted); - key = yield crypto.derive(passphrase); + key = yield this.derive(passphrase); data = crypto.decipher(this.ciphertext, key, this.iv); this.key = HD.fromExtended(data); @@ -2327,6 +2390,23 @@ MasterKey.prototype.stop = function stop() { } }; +/** + * Derive an aes key based on params. + * @param {String|Buffer} passphrase + * @returns {Promise} + */ + +MasterKey.prototype.derive = function derive(passwd) { + switch (this.alg) { + case MasterKey.alg.PBKDF2: + return crypto.pbkdf2Async(passwd, 'bcoin', this.N, 32, 'sha256'); + case MasterKey.alg.SCRYPT: + return crypto.scryptAsync(passwd, 'bcoin', this.N, this.r, this.p, 32); + default: + return Promise.reject(new Error('Unknown algorithm: ' + this.alg)); + } +}; + /** * Encrypt data with in-memory aes key. * @param {Buffer} data @@ -2422,7 +2502,7 @@ MasterKey.prototype._decrypt = co(function* decrypt(passphrase) { this.destroy(); - key = yield crypto.derive(passphrase); + key = yield this.derive(passphrase); data = crypto.decipher(this.ciphertext, key, this.iv); this.key = HD.fromExtended(data); @@ -2469,7 +2549,7 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase) { this.stop(); - key = yield crypto.derive(passphrase); + key = yield this.derive(passphrase); data = crypto.encipher(data, key, iv); this.key = null; @@ -2494,15 +2574,10 @@ MasterKey.prototype.toRaw = function toRaw(writer) { p.writeVarBytes(this.iv); p.writeVarBytes(this.ciphertext); - // Future-proofing: - // algorithm (0=pbkdf2, 1=scrypt) - p.writeU8(0); - // iterations (pbkdf2) / N (scrypt) - p.writeU32(50000); - // r (scrypt) - p.writeU32(0); - // p (scrypt) - p.writeU32(0); + p.writeU8(this.alg); + p.writeU32(this.N); + p.writeU32(this.r); + p.writeU32(this.p); if (!writer) p = p.render(); @@ -2534,11 +2609,13 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) { this.iv = p.readVarBytes(); this.ciphertext = p.readVarBytes(); - // Future-proofing: - assert(p.readU8() === 0); - assert(p.readU32() === 50000); - assert(p.readU32() === 0); - assert(p.readU32() === 0); + this.alg = p.readU8(); + + assert(MasterKey.algByVal[this.alg]); + + this.N = p.readU32(); + this.r = p.readU32(); + this.p = p.readU32(); return this; } @@ -2592,11 +2669,10 @@ MasterKey.prototype.toJSON = function toJSON() { encrypted: true, iv: this.iv.toString('hex'), ciphertext: this.ciphertext.toString('hex'), - // Future-proofing: - algorithm: 'pbkdf2', - N: 50000, - r: 0, - p: 0 + algorithm: MasterKey.algByVal[this.alg], + N: this.N, + r: this.r, + p: this.p }; } @@ -2620,13 +2696,17 @@ MasterKey.prototype.fromJSON = function fromJSON(json) { if (json.encrypted) { assert(typeof json.iv === 'string'); assert(typeof json.ciphertext === 'string'); - // Future-proofing: - assert(json.algorithm === 'pbkdf2'); - assert(json.N === 50000); - assert(json.r === 0); - assert(json.p === 0); + assert(typeof json.algorithm === 'string'); + assert(utils.isNumber(json.N)); + assert(utils.isNumber(json.r)); + assert(utils.isNumber(json.p)); this.iv = new Buffer(json.iv, 'hex'); this.ciphertext = new Buffer(json.ciphertext, 'hex'); + this.alg = MasterKey.alg[json.algorithm]; + assert(this.alg != null); + this.N = json.N; + this.r = json.r; + this.p = json.p; } else { this.key = HD.fromJSON(json.key); }