hd: do not store mnemonic on hd private key.

This commit is contained in:
Christopher Jeffrey 2017-03-11 23:04:31 -08:00
parent d7e2942117
commit aa7e550f91
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 146 additions and 155 deletions

View File

@ -5,8 +5,9 @@ var bench = require('./bench');
var HD = require('../lib/hd'); var HD = require('../lib/hd');
var Mnemonic = require('../lib/hd/mnemonic'); var Mnemonic = require('../lib/hd/mnemonic');
var key = HD.fromMnemonic(); var mnemonic = new Mnemonic();
var phrase = key.mnemonic.getPhrase(); var key = HD.fromMnemonic(mnemonic);
var phrase = mnemonic.getPhrase();
var i, end; var i, end;
assert.equal(Mnemonic.fromPhrase(phrase).getPhrase(), phrase); assert.equal(Mnemonic.fromPhrase(phrase).getPhrase(), phrase);

View File

@ -27,14 +27,12 @@ var HDPublicKey = require('./public');
* @constructor * @constructor
* @param {Object|Base58String} options * @param {Object|Base58String} options
* @param {Base58String?} options.xkey - Serialized base58 key. * @param {Base58String?} options.xkey - Serialized base58 key.
* @param {Mnemonic?} options.mnemonic
* @param {Number?} options.depth * @param {Number?} options.depth
* @param {Buffer?} options.parentFingerPrint * @param {Buffer?} options.parentFingerPrint
* @param {Number?} options.childIndex * @param {Number?} options.childIndex
* @param {Buffer?} options.chainCode * @param {Buffer?} options.chainCode
* @param {Buffer?} options.privateKey * @param {Buffer?} options.privateKey
* @property {Network} network * @property {Network} network
* @property {Mnemonic?} mnemonic
* @property {Number} depth * @property {Number} depth
* @property {Buffer} parentFingerPrint * @property {Buffer} parentFingerPrint
* @property {Number} childIndex * @property {Number} childIndex
@ -56,8 +54,6 @@ function HDPrivateKey(options) {
this.publicKey = encoding.ZERO_KEY; this.publicKey = encoding.ZERO_KEY;
this.fingerPrint = null; this.fingerPrint = null;
this.mnemonic = null;
this._xprivkey = null; this._xprivkey = null;
this._hdPublicKey = null; this._hdPublicKey = null;
@ -91,16 +87,6 @@ HDPrivateKey.prototype.fromOptions = function fromOptions(options) {
this.privateKey = options.privateKey; this.privateKey = options.privateKey;
this.publicKey = ec.publicKeyCreate(options.privateKey, true); this.publicKey = ec.publicKeyCreate(options.privateKey, true);
if (options.mnemonic) {
assert(options.mnemonic instanceof Mnemonic);
this.mnemonic = options.mnemonic;
}
if (options.xprivkey) {
assert(typeof options.xprivkey === 'string');
this._xprivkey = options.xprivkey;
}
return this; return this;
}; };
@ -194,11 +180,6 @@ HDPrivateKey.prototype.destroy = function destroy(pub) {
} }
this._xprivkey = null; this._xprivkey = null;
if (this.mnemonic) {
this.mnemonic.destroy();
this.mnemonic = null;
}
}; };
/** /**
@ -543,21 +524,19 @@ HDPrivateKey.fromSeed = function fromSeed(seed, network) {
/** /**
* Inject properties from a mnemonic. * Inject properties from a mnemonic.
* @private * @private
* @param {Mnemonic|Object} mnemonic * @param {Mnemonic} mnemonic
* @param {(Network|NetworkType)?} network * @param {(Network|NetworkType)?} network
*/ */
HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) { HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) {
if (!(mnemonic instanceof Mnemonic)) assert(mnemonic instanceof Mnemonic);
mnemonic = new Mnemonic(mnemonic);
this.fromSeed(mnemonic.toSeed(), network); this.fromSeed(mnemonic.toSeed(), network);
this.mnemonic = mnemonic;
return this; return this;
}; };
/** /**
* Instantiate an hd private key from a mnemonic. * Instantiate an hd private key from a mnemonic.
* @param {Mnemonic|Object} mnemonic * @param {Mnemonic} mnemonic
* @param {(Network|NetworkType)?} network * @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey} * @returns {HDPrivateKey}
*/ */
@ -566,6 +545,30 @@ HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) {
return new HDPrivateKey().fromMnemonic(mnemonic, network); return new HDPrivateKey().fromMnemonic(mnemonic, network);
}; };
/**
* Inject properties from a mnemonic.
* @private
* @param {String} mnemonic
* @param {(Network|NetworkType)?} network
*/
HDPrivateKey.prototype.fromPhrase = function fromPhrase(phrase, network) {
var mnemonic = Mnemonic.fromPhrase(phrase);
this.fromMnemonic(mnemonic, network);
return this;
};
/**
* Instantiate an hd private key from a phrase.
* @param {String} phrase
* @param {(Network|NetworkType)?} network
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromPhrase = function fromPhrase(phrase, network) {
return new HDPrivateKey().fromPhrase(phrase, network);
};
/** /**
* Inject properties from privateKey and entropy. * Inject properties from privateKey and entropy.
* @private * @private
@ -722,97 +725,6 @@ HDPrivateKey.prototype.toRaw = function toRaw(network) {
return this.toWriter(new StaticWriter(82), network).render(); return this.toWriter(new StaticWriter(82), network).render();
}; };
/**
* Calculate extended serialization size.
* @returns {Number}
*/
HDPrivateKey.prototype.getExtendedSize = function getSize() {
var size = this.getSize();
size += 1;
if (this.mnemonic)
size += this.mnemonic.getSize();
return size;
};
/**
* Write the key in "extended"
* format to a buffer writer.
* @param {BufferWriter} bw
* @param {(Network|NetworkType)?} network
*/
HDPrivateKey.prototype.toExtendedWriter = function toExtendedWriter(bw, network) {
this.toWriter(bw, network);
if (this.mnemonic) {
bw.writeU8(1);
this.mnemonic.toWriter(bw);
} else {
bw.writeU8(0);
}
return bw;
};
/**
* Serialize the key in "extended"
* format (includes the mnemonic).
* @param {(Network|NetworkType)?} network
* @returns {Buffer}
*/
HDPrivateKey.prototype.toExtended = function toExtended(network) {
var size = this.getExtendedSize();
return this.toExtendedWriter(new StaticWriter(size), network).render();
};
/**
* Inject properties from buffer reader.
* @private
* @param {BufferReader} br
*/
HDPrivateKey.prototype.fromExtendedReader = function fromExtendedReader(br) {
this.fromReader(br);
if (br.readU8() === 1)
this.mnemonic = Mnemonic.fromReader(br);
return this;
};
/**
* Inject properties from extended serialized data.
* @private
* @param {Buffer} data
*/
HDPrivateKey.prototype.fromExtended = function fromExtended(data) {
return this.fromExtendedReader(new BufferReader(data));
};
/**
* Instantiate key from "extended" buffer reader.
* @param {BufferReader} br
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromExtendedReader = function fromExtendedReader(br) {
return new HDPrivateKey().fromExtendedReader(br);
};
/**
* Instantiate key from "extended" serialized data.
* @param {Buffer} data
* @returns {HDPrivateKey}
*/
HDPrivateKey.fromExtended = function fromExtended(data) {
return new HDPrivateKey().fromExtended(data);
};
/** /**
* Instantiate an HD private key from a base58 string. * Instantiate an HD private key from a base58 string.
* @param {Base58String} xkey * @param {Base58String} xkey
@ -851,8 +763,7 @@ HDPrivateKey.fromRaw = function fromRaw(raw) {
HDPrivateKey.prototype.toJSON = function toJSON() { HDPrivateKey.prototype.toJSON = function toJSON() {
return { return {
xprivkey: this.xprivkey(), xprivkey: this.xprivkey()
mnemonic: this.mnemonic ? this.mnemonic.toJSON() : null
}; };
}; };
@ -868,9 +779,6 @@ HDPrivateKey.prototype.fromJSON = function fromJSON(json, network) {
this.fromBase58(json.xprivkey, network); this.fromBase58(json.xprivkey, network);
if (json.mnemonic)
this.mnemonic = Mnemonic.fromJSON(json.mnemonic);
return this; return this;
}; };
@ -894,7 +802,7 @@ HDPrivateKey.fromJSON = function fromJSON(json, network) {
HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) { HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) {
return obj return obj
&& typeof obj.derive === 'function' && typeof obj.derive === 'function'
&& typeof obj.toExtended === 'function' && typeof obj.fromMnemonic === 'function'
&& obj.chainCode !== undefined; && obj.chainCode !== undefined;
}; };

View File

@ -79,11 +79,6 @@ HDPublicKey.prototype.fromOptions = function fromOptions(options) {
this.chainCode = options.chainCode; this.chainCode = options.chainCode;
this.publicKey = options.publicKey; this.publicKey = options.publicKey;
if (options.xpubkey) {
assert(typeof options.xpubkey === 'string');
this._xpubkey = options.xpubkey;
}
return this; return this;
}; };

View File

@ -15,6 +15,7 @@ var BufferReader = require('../utils/reader');
var StaticWriter = require('../utils/staticwriter'); var StaticWriter = require('../utils/staticwriter');
var encoding = require('../utils/encoding'); var encoding = require('../utils/encoding');
var HD = require('../hd/hd'); var HD = require('../hd/hd');
var Mnemonic = HD.Mnemonic;
/** /**
* Master BIP32 key which can exist * Master BIP32 key which can exist
@ -32,6 +33,7 @@ function MasterKey(options) {
this.iv = null; this.iv = null;
this.ciphertext = null; this.ciphertext = null;
this.key = null; this.key = null;
this.mnemonic = null;
this.alg = MasterKey.alg.PBKDF2; this.alg = MasterKey.alg.PBKDF2;
this.N = 50000; this.N = 50000;
@ -103,10 +105,15 @@ MasterKey.prototype.fromOptions = function fromOptions(options) {
} }
if (options.key) { if (options.key) {
assert(HD.isHD(options.key)); assert(HD.isPrivate(options.key));
this.key = options.key; this.key = options.key;
} }
if (options.mnemonic) {
assert(options.mnemonic instanceof Mnemonic);
this.mnemonic = options.mnemonic;
}
if (options.alg != null) { if (options.alg != null) {
if (typeof options.alg === 'string') { if (typeof options.alg === 'string') {
this.alg = MasterKey.alg[options.alg.toUpperCase()]; this.alg = MasterKey.alg[options.alg.toUpperCase()];
@ -195,7 +202,7 @@ MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) {
key = yield this.derive(passphrase); key = yield this.derive(passphrase);
data = crypto.decipher(this.ciphertext, key, this.iv); data = crypto.decipher(this.ciphertext, key, this.iv);
this.key = HD.fromExtended(data); this.fromKeyRaw(data);
this.start(timeout); this.start(timeout);
@ -382,7 +389,7 @@ MasterKey.prototype._decrypt = co(function* decrypt(passphrase, clean) {
key = yield this.derive(passphrase); key = yield this.derive(passphrase);
data = crypto.decipher(this.ciphertext, key, this.iv); data = crypto.decipher(this.ciphertext, key, this.iv);
this.key = HD.fromExtended(data); this.fromKeyRaw(data);
this.encrypted = false; this.encrypted = false;
this.iv = null; this.iv = null;
this.ciphertext = null; this.ciphertext = null;
@ -426,7 +433,7 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase, clean) {
if (!passphrase) if (!passphrase)
throw new Error('No passphrase provided.'); throw new Error('No passphrase provided.');
data = this.key.toExtended(); data = this.toKeyRaw();
iv = crypto.randomBytes(16); iv = crypto.randomBytes(16);
this.stop(); this.stop();
@ -435,6 +442,7 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase, clean) {
data = crypto.encipher(data, key, iv); data = crypto.encipher(data, key, iv);
this.key = null; this.key = null;
this.mnemonic = null;
this.encrypted = true; this.encrypted = true;
this.iv = iv; this.iv = iv;
this.ciphertext = data; this.ciphertext = data;
@ -447,6 +455,59 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase, clean) {
return key; return key;
}); });
/**
* Calculate key serialization size.
* @returns {Number}
*/
MasterKey.prototype.getKeySize = function getKeySize() {
var size = 0;
size += this.key.getSize();
size += 1;
if (this.mnemonic)
size += this.mnemonic.getSize();
return size;
};
/**
* Serialize key and menmonic to a single buffer.
* @returns {Buffer}
*/
MasterKey.prototype.toKeyRaw = function toKeyRaw() {
var bw = new StaticWriter(this.getKeySize());
this.key.toWriter(bw);
if (this.mnemonic) {
bw.writeU8(1);
this.mnemonic.toWriter(bw);
} else {
bw.writeU8(0);
}
return bw.render();
};
/**
* Inject properties from serialized key.
* @param {Buffer} data
*/
MasterKey.prototype.fromKeyRaw = function fromKeyRaw(data) {
var br = new BufferReader(data);
this.key = HD.fromReader(br);
if (br.readU8() === 1)
this.mnemonic = Mnemonic.fromReader(br);
return this;
};
/** /**
* Calculate serialization size. * Calculate serialization size.
* @returns {Number} * @returns {Number}
@ -464,7 +525,7 @@ MasterKey.prototype.getSize = function getSize() {
} }
size += 1; size += 1;
size += encoding.sizeVarlen(this.key.getExtendedSize()); size += encoding.sizeVarlen(this.getKeySize());
return size; return size;
}; };
@ -493,7 +554,19 @@ MasterKey.prototype.toRaw = function toRaw() {
} }
bw.writeU8(0); bw.writeU8(0);
bw.writeVarBytes(this.key.toExtended());
// NOTE: useless varint
size = this.getKeySize();
bw.writeVarint(size);
bw.writeBytes(this.key.toRaw());
if (this.mnemonic) {
bw.writeU8(1);
this.mnemonic.toWriter(bw);
} else {
bw.writeU8(0);
}
return bw.render(); return bw.render();
}; };
@ -524,7 +597,13 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) {
return this; return this;
} }
this.key = HD.fromExtended(br.readVarBytes()); // NOTE: useless varint
br.skipVarint();
this.key = HD.fromRaw(br.readBytes(82));
if (br.readU8() === 1)
this.mnemonic = Mnemonic.fromReader(br);
return this; return this;
}; };
@ -542,24 +621,27 @@ MasterKey.fromRaw = function fromRaw(raw) {
* Inject properties from an HDPrivateKey. * Inject properties from an HDPrivateKey.
* @private * @private
* @param {HDPrivateKey} key * @param {HDPrivateKey} key
* @param {Mnemonic?} mnemonic
*/ */
MasterKey.prototype.fromKey = function fromKey(key) { MasterKey.prototype.fromKey = function fromKey(key, mnemonic) {
this.encrypted = false; this.encrypted = false;
this.iv = null; this.iv = null;
this.ciphertext = null; this.ciphertext = null;
this.key = key; this.key = key;
this.mnemonic = mnemonic || null;
return this; return this;
}; };
/** /**
* Instantiate master key from an HDPrivateKey. * Instantiate master key from an HDPrivateKey.
* @param {HDPrivateKey} key * @param {HDPrivateKey} key
* @param {Mnemonic?} mnemonic
* @returns {MasterKey} * @returns {MasterKey}
*/ */
MasterKey.fromKey = function fromKey(key) { MasterKey.fromKey = function fromKey(key, mnemonic) {
return new MasterKey().fromKey(key); return new MasterKey().fromKey(key, mnemonic);
}; };
/** /**
@ -585,7 +667,8 @@ MasterKey.prototype.toJSON = function toJSON(unsafe) {
return { return {
encrypted: false, encrypted: false,
key: unsafe ? this.key.toJSON() : undefined key: unsafe ? this.key.toJSON() : undefined,
mnemonic: unsafe && this.mnemonic ? this.mnemonic.toJSON() : undefined
}; };
}; };
@ -596,8 +679,13 @@ MasterKey.prototype.toJSON = function toJSON(unsafe) {
MasterKey.prototype.inspect = function inspect() { MasterKey.prototype.inspect = function inspect() {
var json = this.toJSON(true); var json = this.toJSON(true);
if (this.key) if (this.key)
json.key = this.key.toJSON(); json.key = this.key.toJSON();
if (this.mnemonic)
json.mnemonic = this.mnemonic.toJSON();
return json; return json;
}; };

View File

@ -31,6 +31,7 @@ var MasterKey = require('./masterkey');
var LRU = require('../utils/lru'); var LRU = require('../utils/lru');
var policy = require('../protocol/policy'); var policy = require('../protocol/policy');
var consensus = require('../protocol/consensus'); var consensus = require('../protocol/consensus');
var Mnemonic = HD.Mnemonic;
/** /**
* BIP44 Wallet * BIP44 Wallet
@ -102,26 +103,24 @@ util.inherits(Wallet, EventEmitter);
Wallet.prototype.fromOptions = function fromOptions(options) { Wallet.prototype.fromOptions = function fromOptions(options) {
var key = options.master; var key = options.master;
var id, token; var id, token, mnemonic;
if (MasterKey.isMasterKey(key)) { if (key) {
this.master.fromOptions(key); if (typeof key === 'string')
} else {
if (!key)
key = HD.fromMnemonic(null, this.network);
if (HD.isBase58(key))
key = HD.fromBase58(key, this.network); key = HD.fromBase58(key, this.network);
assert(HD.isPrivate(key), assert(HD.isPrivate(key),
'Must create wallet with hd private key.'); 'Must create wallet with hd private key.');
} else {
assert(key.verifyNetwork(this.network), mnemonic = new Mnemonic(options.mnemonic);
'Network mismatch for master key.'); key = HD.fromMnemonic(mnemonic, this.network);
this.master.fromKey(key);
} }
assert(key.verifyNetwork(this.network),
'Network mismatch for master key.');
this.master.fromKey(key, mnemonic);
if (options.wid != null) { if (options.wid != null) {
assert(util.isNumber(options.wid)); assert(util.isNumber(options.wid));
this.wid = options.wid; this.wid = options.wid;

View File

@ -87,7 +87,7 @@ describe('HD', function() {
}); });
it('should deserialize and reserialize', function() { it('should deserialize and reserialize', function() {
var key = HD.fromMnemonic(); var key = HD.generate();
assert.equal(HD.fromJSON(key.toJSON()).toBase58(), key.toBase58()); assert.equal(HD.fromJSON(key.toJSON()).toBase58(), key.toBase58());
}); });

View File

@ -87,7 +87,7 @@ MemWallet.prototype.init = function init() {
var i; var i;
if (!this.master) if (!this.master)
this.master = HD.PrivateKey.fromMnemonic(null, this.network); this.master = HD.PrivateKey.generate();
if (!this.key) if (!this.key)
this.key = this.master.deriveAccount44(this.account); this.key = this.master.deriveAccount44(this.account);

View File

@ -154,7 +154,7 @@ describe('Wallet', function() {
n: 2 n: 2
}); });
k = HD.fromMnemonic().deriveAccount44(0).toPublic(); k = HD.generate().deriveAccount44(0).toPublic();
yield w.addSharedKey(k); yield w.addSharedKey(k);