wallet: more migrations.

This commit is contained in:
Christopher Jeffrey 2017-11-27 10:46:38 -08:00
parent 0055c82f22
commit e2ef35b24b
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 373 additions and 148 deletions

View File

@ -41,8 +41,6 @@ class Mnemonic {
* if not present. * if not present.
* @param {String?} options.phrase - Mnemonic phrase (will * @param {String?} options.phrase - Mnemonic phrase (will
* be generated if not present). * be generated if not present).
* @param {String?} options.passphrase - Optional salt for
* key stretching (empty string if not present).
* @param {String?} options.language - Language. * @param {String?} options.language - Language.
*/ */
@ -51,7 +49,6 @@ class Mnemonic {
this.language = 'english'; this.language = 'english';
this.entropy = null; this.entropy = null;
this.phrase = null; this.phrase = null;
this.passphrase = '';
if (options) if (options)
this.fromOptions(options); this.fromOptions(options);
@ -81,11 +78,6 @@ class Mnemonic {
this.language = options.language; this.language = options.language;
} }
if (options.passphrase) {
assert(typeof options.passphrase === 'string');
this.passphrase = options.passphrase;
}
if (options.phrase) { if (options.phrase) {
this.fromPhrase(options.phrase); this.fromPhrase(options.phrase);
return this; return this;
@ -121,7 +113,6 @@ class Mnemonic {
this.entropy = null; this.entropy = null;
} }
this.phrase = null; this.phrase = null;
this.passphrase = '';
} }
/** /**
@ -132,12 +123,10 @@ class Mnemonic {
toSeed(passphrase) { toSeed(passphrase) {
if (!passphrase) if (!passphrase)
passphrase = this.passphrase; passphrase = '';
this.passphrase = passphrase;
const phrase = nfkd(this.getPhrase()); const phrase = nfkd(this.getPhrase());
const passwd = nfkd('mnemonic' + passphrase); const passwd = nfkd(`mnemonic${passphrase}`);
return pbkdf2.derive(sha512, return pbkdf2.derive(sha512,
Buffer.from(phrase, 'utf8'), Buffer.from(phrase, 'utf8'),
@ -375,8 +364,7 @@ class Mnemonic {
bits: this.bits, bits: this.bits,
language: this.language, language: this.language,
entropy: this.getEntropy().toString('hex'), entropy: this.getEntropy().toString('hex'),
phrase: this.getPhrase(), phrase: this.getPhrase()
passphrase: this.passphrase
}; };
} }
@ -392,7 +380,6 @@ class Mnemonic {
assert(typeof json.language === 'string'); assert(typeof json.language === 'string');
assert(typeof json.entropy === 'string'); assert(typeof json.entropy === 'string');
assert(typeof json.phrase === 'string'); assert(typeof json.phrase === 'string');
assert(typeof json.passphrase === 'string');
assert(json.bits >= common.MIN_ENTROPY); assert(json.bits >= common.MIN_ENTROPY);
assert(json.bits <= common.MAX_ENTROPY); assert(json.bits <= common.MAX_ENTROPY);
assert(json.bits % 32 === 0); assert(json.bits % 32 === 0);
@ -402,7 +389,6 @@ class Mnemonic {
this.language = json.language; this.language = json.language;
this.entropy = Buffer.from(json.entropy, 'hex'); this.entropy = Buffer.from(json.entropy, 'hex');
this.phrase = json.phrase; this.phrase = json.phrase;
this.passphrase = json.passphrase;
return this; return this;
} }
@ -426,8 +412,6 @@ class Mnemonic {
let size = 0; let size = 0;
size += 3; size += 3;
size += this.getEntropy().length; size += this.getEntropy().length;
size += encoding.sizeVarString(this.getPhrase(), 'utf8');
size += encoding.sizeVarString(this.passphrase, 'utf8');
return size; return size;
} }
@ -444,8 +428,6 @@ class Mnemonic {
bw.writeU16(this.bits); bw.writeU16(this.bits);
bw.writeU8(lang); bw.writeU8(lang);
bw.writeBytes(this.getEntropy()); bw.writeBytes(this.getEntropy());
bw.writeVarString(this.getPhrase(), 'utf8');
bw.writeVarString(this.passphrase, 'utf8');
return bw; return bw;
} }
@ -479,8 +461,6 @@ class Mnemonic {
this.bits = bits; this.bits = bits;
this.language = language; this.language = language;
this.entropy = br.readBytes(bits / 8); this.entropy = br.readBytes(bits / 8);
this.phrase = br.readVarString('utf8');
this.passphrase = br.readVarString('utf8');
return this; return this;
} }

View File

@ -460,21 +460,23 @@ class HDPrivateKey {
* Inject properties from a mnemonic. * Inject properties from a mnemonic.
* @private * @private
* @param {Mnemonic} mnemonic * @param {Mnemonic} mnemonic
* @param {String?} passphrase
*/ */
fromMnemonic(mnemonic) { fromMnemonic(mnemonic, passphrase) {
assert(mnemonic instanceof Mnemonic); assert(mnemonic instanceof Mnemonic);
return this.fromSeed(mnemonic.toSeed()); return this.fromSeed(mnemonic.toSeed(passphrase));
} }
/** /**
* Instantiate an hd private key from a mnemonic. * Instantiate an hd private key from a mnemonic.
* @param {Mnemonic} mnemonic * @param {Mnemonic} mnemonic
* @param {String?} passphrase
* @returns {HDPrivateKey} * @returns {HDPrivateKey}
*/ */
static fromMnemonic(mnemonic) { static fromMnemonic(mnemonic, passphrase) {
return new this().fromMnemonic(mnemonic); return new this().fromMnemonic(mnemonic, passphrase);
} }
/** /**

View File

@ -13,7 +13,7 @@ const Path = require('./path');
const common = require('./common'); const common = require('./common');
const Script = require('../script/script'); const Script = require('../script/script');
const WalletKey = require('./walletkey'); const WalletKey = require('./walletkey');
const HD = require('../hd/hd'); const {HDPublicKey} = require('../hd/hd');
const {encoding} = bio; const {encoding} = bio;
/** /**
@ -40,6 +40,7 @@ class Account {
this.wid = 0; this.wid = 0;
this.id = null; this.id = null;
this.accountIndex = 0;
this.name = null; this.name = null;
this.initialized = false; this.initialized = false;
this.witness = wdb.options.witness === true; this.witness = wdb.options.witness === true;
@ -47,7 +48,6 @@ class Account {
this.type = Account.types.PUBKEYHASH; this.type = Account.types.PUBKEYHASH;
this.m = 1; this.m = 1;
this.n = 1; this.n = 1;
this.accountIndex = 0;
this.receiveDepth = 0; this.receiveDepth = 0;
this.changeDepth = 0; this.changeDepth = 0;
this.nestedDepth = 0; this.nestedDepth = 0;
@ -69,13 +69,19 @@ class Account {
assert(options, 'Options are required.'); assert(options, 'Options are required.');
assert((options.wid >>> 0) === options.wid); assert((options.wid >>> 0) === options.wid);
assert(common.isName(options.id), 'Bad Wallet ID.'); assert(common.isName(options.id), 'Bad Wallet ID.');
assert(HD.isHD(options.accountKey), 'Account key is required.'); assert(HDPublicKey.isHDPublicKey(options.accountKey),
'Account key is required.');
assert((options.accountIndex >>> 0) === options.accountIndex, assert((options.accountIndex >>> 0) === options.accountIndex,
'Account index is required.'); 'Account index is required.');
this.wid = options.wid; this.wid = options.wid;
this.id = options.id; this.id = options.id;
if (options.accountIndex != null) {
assert((options.accountIndex >>> 0) === options.accountIndex);
this.accountIndex = options.accountIndex;
}
if (options.name != null) { if (options.name != null) {
assert(common.isName(options.name), 'Bad account name.'); assert(common.isName(options.name), 'Bad account name.');
this.name = options.name; this.name = options.name;
@ -117,11 +123,6 @@ class Account {
this.n = options.n; this.n = options.n;
} }
if (options.accountIndex != null) {
assert((options.accountIndex >>> 0) === options.accountIndex);
this.accountIndex = options.accountIndex;
}
if (options.receiveDepth != null) { if (options.receiveDepth != null) {
assert((options.receiveDepth >>> 0) === options.receiveDepth); assert((options.receiveDepth >>> 0) === options.receiveDepth);
this.receiveDepth = options.receiveDepth; this.receiveDepth = options.receiveDepth;
@ -210,9 +211,9 @@ class Account {
pushKey(key) { pushKey(key) {
if (typeof key === 'string') if (typeof key === 'string')
key = HD.PublicKey.fromBase58(key, this.network); key = HDPublicKey.fromBase58(key, this.network);
if (!HD.isPublic(key)) if (!HDPublicKey.isHDPublicKey(key))
throw new Error('Must add HD keys to wallet.'); throw new Error('Must add HD keys to wallet.');
if (!key.isAccount()) if (!key.isAccount())
@ -247,9 +248,9 @@ class Account {
spliceKey(key) { spliceKey(key) {
if (typeof key === 'string') if (typeof key === 'string')
key = HD.PublicKey.fromBase58(key, this.network); key = HDPublicKey.fromBase58(key, this.network);
if (!HD.isPublic(key)) if (!HDPublicKey.isHDPublicKey(key))
throw new Error('Must add HD keys to wallet.'); throw new Error('Must add HD keys to wallet.');
if (!key.isAccount()) if (!key.isAccount())
@ -455,7 +456,7 @@ class Account {
/** /**
* Derive an address at `index`. Do not increment depth. * Derive an address at `index`. Do not increment depth.
* @param {Number} branch - Whether the address on the change branch. * @param {Number} branch
* @param {Number} index * @param {Number} index
* @returns {WalletKey} * @returns {WalletKey}
*/ */
@ -842,8 +843,8 @@ class Account {
getSize() { getSize() {
let size = 0; let size = 0;
size += encoding.sizeVarString(this.name, 'ascii'); size += encoding.sizeVarString(this.name, 'ascii');
size += 105; size += 96;
size += this.keys.length * 82; size += this.keys.length * 74;
return size; return size;
} }
@ -856,22 +857,29 @@ class Account {
const size = this.getSize(); const size = this.getSize();
const bw = bio.write(size); const bw = bio.write(size);
let flags = 0;
if (this.initialized)
flags |= 1;
if (this.witness)
flags |= 2;
bw.writeU32(this.accountIndex);
bw.writeVarString(this.name, 'ascii'); bw.writeVarString(this.name, 'ascii');
bw.writeU8(this.initialized ? 1 : 0); bw.writeU8(flags);
bw.writeU8(this.witness ? 1 : 0);
bw.writeU8(this.type); bw.writeU8(this.type);
bw.writeU8(this.m); bw.writeU8(this.m);
bw.writeU8(this.n); bw.writeU8(this.n);
bw.writeU32(this.accountIndex);
bw.writeU32(this.receiveDepth); bw.writeU32(this.receiveDepth);
bw.writeU32(this.changeDepth); bw.writeU32(this.changeDepth);
bw.writeU32(this.nestedDepth); bw.writeU32(this.nestedDepth);
bw.writeU8(this.lookahead); bw.writeU8(this.lookahead);
bw.writeBytes(this.accountKey.toRaw(this.network)); writeKey(this.accountKey, bw);
bw.writeU8(this.keys.length); bw.writeU8(this.keys.length);
for (const key of this.keys) for (const key of this.keys)
bw.writeBytes(key.toRaw(this.network)); writeKey(key, bw);
return bw.render(); return bw.render();
} }
@ -886,26 +894,29 @@ class Account {
fromRaw(data) { fromRaw(data) {
const br = bio.read(data); const br = bio.read(data);
this.accountIndex = br.readU32();
this.name = br.readVarString('ascii'); this.name = br.readVarString('ascii');
this.initialized = br.readU8() === 1;
this.witness = br.readU8() === 1; const flags = br.readU8();
this.initialized = (flags & 1) !== 0;
this.witness = (flags & 2) !== 0;
this.type = br.readU8(); this.type = br.readU8();
this.m = br.readU8(); this.m = br.readU8();
this.n = br.readU8(); this.n = br.readU8();
this.accountIndex = br.readU32();
this.receiveDepth = br.readU32(); this.receiveDepth = br.readU32();
this.changeDepth = br.readU32(); this.changeDepth = br.readU32();
this.nestedDepth = br.readU32(); this.nestedDepth = br.readU32();
this.lookahead = br.readU8(); this.lookahead = br.readU8();
this.accountKey = HD.PublicKey.fromRaw(br.readBytes(82), this.network); this.accountKey = readKey(br);
assert(Account.typesByVal[this.type]); assert(this.type < Account.typesByVal.length);
const count = br.readU8(); const count = br.readU8();
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const key = HD.PublicKey.fromRaw(br.readBytes(82), this.network); const key = readKey(br);
this.pushKey(key); binary.insert(this.keys, key, cmp, true);
} }
return this; return this;
@ -954,7 +965,7 @@ Account.typesByVal = [
'multisig' 'multisig'
]; ];
/* /**
* Default address lookahead. * Default address lookahead.
* @const {Number} * @const {Number}
*/ */
@ -969,6 +980,24 @@ function cmp(a, b) {
return a.compare(b); return a.compare(b);
} }
function writeKey(key, bw) {
bw.writeU8(key.depth);
bw.writeU32BE(key.parentFingerPrint);
bw.writeU32BE(key.childIndex);
bw.writeBytes(key.chainCode);
bw.writeBytes(key.publicKey);
}
function readKey(br) {
const key = new HDPublicKey();
key.depth = br.readU8();
key.parentFingerPrint = br.readU32BE();
key.childIndex = br.readU32BE();
key.chainCode = br.readBytes(32);
key.publicKey = br.readBytes(33);
return key;
}
/* /*
* Expose * Expose
*/ */

View File

@ -211,7 +211,7 @@ class HTTP extends Server {
// Get wallet master key // Get wallet master key
this.get('/:id/master', (req, res) => { this.get('/:id/master', (req, res) => {
res.json(200, req.wallet.master.toJSON(true)); res.json(200, req.wallet.master.toJSON(this.network, true));
}); });
// Create wallet // Create wallet

View File

@ -13,13 +13,13 @@ const random = require('bcrypto/lib/random');
const cleanse = require('bcrypto/lib/cleanse'); const cleanse = require('bcrypto/lib/cleanse');
const aes = require('bcrypto/lib/aes'); const aes = require('bcrypto/lib/aes');
const sha256 = require('bcrypto/lib/sha256'); const sha256 = require('bcrypto/lib/sha256');
const hash256 = require('bcrypto/lib/hash256');
const secp256k1 = require('bcrypto/lib/secp256k1');
const pbkdf2 = require('bcrypto/lib/pbkdf2'); const pbkdf2 = require('bcrypto/lib/pbkdf2');
const scrypt = require('bcrypto/lib/scrypt'); const scrypt = require('bcrypto/lib/scrypt');
const Network = require('../protocol/network');
const util = require('../utils/util'); const util = require('../utils/util');
const HD = require('../hd/hd'); const {HDPrivateKey, Mnemonic} = require('../hd/hd');
const {encoding} = bio; const {encoding} = bio;
const {Mnemonic} = HD;
/** /**
* Master Key * Master Key
@ -43,14 +43,13 @@ class MasterKey {
this.mnemonic = null; this.mnemonic = null;
this.alg = MasterKey.alg.PBKDF2; this.alg = MasterKey.alg.PBKDF2;
this.N = 50000; this.n = 50000;
this.r = 0; this.r = 0;
this.p = 0; this.p = 0;
this.aesKey = null; this.aesKey = null;
this.timer = null; this.timer = null;
this.until = 0; this.until = 0;
this._onTimeout = this.lock.bind(this);
this.locker = new Lock(); this.locker = new Lock();
if (options) if (options)
@ -66,9 +65,6 @@ class MasterKey {
fromOptions(options) { fromOptions(options) {
assert(options); assert(options);
if (options.network != null)
this.network = Network.get(options.network);
if (options.encrypted != null) { if (options.encrypted != null) {
assert(typeof options.encrypted === 'boolean'); assert(typeof options.encrypted === 'boolean');
this.encrypted = options.encrypted; this.encrypted = options.encrypted;
@ -85,7 +81,7 @@ class MasterKey {
} }
if (options.key) { if (options.key) {
assert(HD.isPrivate(options.key)); assert(HDPrivateKey.isHDPrivateKey(options.key));
this.key = options.key; this.key = options.key;
} }
@ -107,12 +103,12 @@ class MasterKey {
if (options.rounds != null) { if (options.rounds != null) {
assert((options.rounds >>> 0) === options.rounds); assert((options.rounds >>> 0) === options.rounds);
this.N = options.rounds; this.n = options.rounds;
} }
if (options.N != null) { if (options.n != null) {
assert((options.N >>> 0) === options.N); assert((options.n >>> 0) === options.n);
this.N = options.N; this.n = options.n;
} }
if (options.r != null) { if (options.r != null) {
@ -192,7 +188,7 @@ class MasterKey {
/** /**
* Start the destroy timer. * Start the destroy timer.
* @private * @private
* @param {Number} [timeout=60000] timeout in ms. * @param {Number} [timeout=60] timeout in seconds.
*/ */
start(timeout) { start(timeout) {
@ -204,8 +200,10 @@ class MasterKey {
if (timeout === -1) if (timeout === -1)
return; return;
assert((timeout >>> 0) === timeout);
this.until = util.now() + timeout; this.until = util.now() + timeout;
this.timer = setTimeout(this._onTimeout, timeout * 1000); this.timer = setTimeout(() => this.lock(), timeout * 1000);
} }
/** /**
@ -229,7 +227,7 @@ class MasterKey {
async derive(passwd) { async derive(passwd) {
const salt = MasterKey.SALT; const salt = MasterKey.SALT;
const N = this.N; const n = this.n;
const r = this.r; const r = this.r;
const p = this.p; const p = this.p;
@ -238,9 +236,9 @@ class MasterKey {
switch (this.alg) { switch (this.alg) {
case MasterKey.alg.PBKDF2: case MasterKey.alg.PBKDF2:
return await pbkdf2.deriveAsync(sha256, passwd, salt, N, 32); return pbkdf2.deriveAsync(sha256, passwd, salt, n, 32);
case MasterKey.alg.SCRYPT: case MasterKey.alg.SCRYPT:
return await scrypt.deriveAsync(passwd, salt, N, r, p, 32); return scrypt.deriveAsync(passwd, salt, n, r, p, 32);
default: default:
throw new Error(`Unknown algorithm: ${this.alg}.`); throw new Error(`Unknown algorithm: ${this.alg}.`);
} }
@ -437,7 +435,7 @@ class MasterKey {
keySize() { keySize() {
let size = 0; let size = 0;
size += this.key.getSize(); size += 64;
size += 1; size += 1;
if (this.mnemonic) if (this.mnemonic)
@ -454,7 +452,8 @@ class MasterKey {
writeKey() { writeKey() {
const bw = bio.write(this.keySize()); const bw = bio.write(this.keySize());
this.key.toWriter(bw, this.network); bw.writeBytes(this.key.chainCode);
bw.writeBytes(this.key.privateKey);
if (this.mnemonic) { if (this.mnemonic) {
bw.writeU8(1); bw.writeU8(1);
@ -474,7 +473,19 @@ class MasterKey {
readKey(data) { readKey(data) {
const br = bio.read(data); const br = bio.read(data);
this.key = HD.PrivateKey.fromReader(br, this.network); this.key = new HDPrivateKey();
if (isLegacy(data)) {
br.seek(13);
this.key.chainCode = br.readBytes(32);
assert(br.readU8() === 0);
this.key.privateKey = br.readBytes(32);
} else {
this.key.chainCode = br.readBytes(32);
this.key.privateKey = br.readBytes(32);
}
this.key.publicKey = secp256k1.publicKeyCreate(this.key.privateKey, true);
if (br.readU8() === 1) if (br.readU8() === 1)
this.mnemonic = Mnemonic.fromReader(br); this.mnemonic = Mnemonic.fromReader(br);
@ -499,7 +510,7 @@ class MasterKey {
} }
size += 1; size += 1;
size += encoding.sizeVarlen(this.keySize()); size += this.keySize();
return size; return size;
} }
@ -510,29 +521,24 @@ class MasterKey {
* @returns {Buffer} * @returns {Buffer}
*/ */
toRaw() { toWriter(bw) {
const bw = bio.write(this.getSize());
if (this.encrypted) { if (this.encrypted) {
bw.writeU8(1); bw.writeU8(1);
bw.writeVarBytes(this.iv); bw.writeVarBytes(this.iv);
bw.writeVarBytes(this.ciphertext); bw.writeVarBytes(this.ciphertext);
bw.writeU8(this.alg); bw.writeU8(this.alg);
bw.writeU32(this.N); bw.writeU32(this.n);
bw.writeU32(this.r); bw.writeU32(this.r);
bw.writeU32(this.p); bw.writeU32(this.p);
return bw.render(); return bw;
} }
bw.writeU8(0); bw.writeU8(0);
// NOTE: useless varint bw.writeBytes(this.key.chainCode);
const size = this.keySize(); bw.writeBytes(this.key.privateKey);
bw.writeVarint(size);
bw.writeBytes(this.key.toRaw(this.network));
if (this.mnemonic) { if (this.mnemonic) {
bw.writeU8(1); bw.writeU8(1);
@ -541,7 +547,18 @@ class MasterKey {
bw.writeU8(0); bw.writeU8(0);
} }
return bw.render(); return bw;
}
/**
* Serialize the key in the form of:
* `[enc-flag][iv?][ciphertext?][extended-key?]`
* @returns {Buffer}
*/
toRaw() {
const size = this.getSize();
return this.toWriter(bio.write(size)).render();
} }
/** /**
@ -550,10 +567,7 @@ class MasterKey {
* @param {Buffer} raw * @param {Buffer} raw
*/ */
fromRaw(raw, network) { fromReader(br) {
const br = bio.read(raw);
this.network = Network.get(network);
this.encrypted = br.readU8() === 1; this.encrypted = br.readU8() === 1;
if (this.encrypted) { if (this.encrypted) {
@ -564,17 +578,17 @@ class MasterKey {
assert(MasterKey.algByVal[this.alg]); assert(MasterKey.algByVal[this.alg]);
this.N = br.readU32(); this.n = br.readU32();
this.r = br.readU32(); this.r = br.readU32();
this.p = br.readU32(); this.p = br.readU32();
return this; return this;
} }
// NOTE: useless varint this.key = new HDPrivateKey();
br.readVarint(); this.key.chainCode = br.readBytes(32);
this.key.privateKey = br.readBytes(32);
this.key = HD.PrivateKey.fromRaw(br.readBytes(82), this.network); this.key.publicKey = secp256k1.publicKeyCreate(this.key.privateKey, true);
if (br.readU8() === 1) if (br.readU8() === 1)
this.mnemonic = Mnemonic.fromReader(br); this.mnemonic = Mnemonic.fromReader(br);
@ -587,8 +601,27 @@ class MasterKey {
* @returns {MasterKey} * @returns {MasterKey}
*/ */
static fromRaw(raw, network) { static fromReader(br) {
return new this().fromRaw(raw, network); return new this().fromReader(br);
}
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} raw
*/
fromRaw(raw) {
return this.fromReader(bio.read(raw));
}
/**
* Instantiate master key from serialized data.
* @returns {MasterKey}
*/
static fromRaw(raw) {
return new this().fromRaw(raw);
} }
/** /**
@ -598,13 +631,12 @@ class MasterKey {
* @param {Mnemonic?} mnemonic * @param {Mnemonic?} mnemonic
*/ */
fromKey(key, mnemonic, network) { 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; this.mnemonic = mnemonic || null;
this.network = Network.get(network);
return this; return this;
} }
@ -615,18 +647,19 @@ class MasterKey {
* @returns {MasterKey} * @returns {MasterKey}
*/ */
static fromKey(key, mnemonic, network) { static fromKey(key, mnemonic) {
return new this().fromKey(key, mnemonic, network); return new this().fromKey(key, mnemonic);
} }
/** /**
* Convert master key to a jsonifiable object. * Convert master key to a jsonifiable object.
* @param {Network?} network
* @param {Boolean?} unsafe - Whether to include * @param {Boolean?} unsafe - Whether to include
* the key data in the JSON. * the key data in the JSON.
* @returns {Object} * @returns {Object}
*/ */
toJSON(unsafe) { toJSON(network, unsafe) {
if (this.encrypted) { if (this.encrypted) {
return { return {
encrypted: true, encrypted: true,
@ -634,7 +667,7 @@ class MasterKey {
iv: this.iv.toString('hex'), iv: this.iv.toString('hex'),
ciphertext: unsafe ? this.ciphertext.toString('hex') : undefined, ciphertext: unsafe ? this.ciphertext.toString('hex') : undefined,
algorithm: MasterKey.algByVal[this.alg].toLowerCase(), algorithm: MasterKey.algByVal[this.alg].toLowerCase(),
N: this.N, n: this.n,
r: this.r, r: this.r,
p: this.p p: this.p
}; };
@ -642,7 +675,7 @@ class MasterKey {
return { return {
encrypted: false, encrypted: false,
key: unsafe ? this.key.toJSON(this.network) : undefined, key: unsafe ? this.key.toJSON(network) : undefined,
mnemonic: unsafe && this.mnemonic ? this.mnemonic.toJSON() : undefined mnemonic: unsafe && this.mnemonic ? this.mnemonic.toJSON() : undefined
}; };
} }
@ -653,10 +686,10 @@ class MasterKey {
*/ */
inspect() { inspect() {
const json = this.toJSON(true); const json = this.toJSON(null, true);
if (this.key) if (this.key)
json.key = this.key.toJSON(this.network); json.key = this.key.toJSON();
if (this.mnemonic) if (this.mnemonic)
json.mnemonic = this.mnemonic.toJSON(); json.mnemonic = this.mnemonic.toJSON();
@ -705,6 +738,22 @@ MasterKey.algByVal = [
'SCRYPT' 'SCRYPT'
]; ];
/*
* Helpers
*/
function isLegacy(data) {
if (data.length < 82)
return false;
const key = data.slice(0, 78);
const chk = data.readUInt32LE(78, true);
const hash = hash256.digest(key);
return hash.readUInt32LE(0, true) === chk;
}
/* /*
* Expose * Expose
*/ */

View File

@ -59,7 +59,6 @@ class Wallet extends EventEmitter {
this.wid = 0; this.wid = 0;
this.id = null; this.id = null;
this.initialized = false;
this.watchOnly = false; this.watchOnly = false;
this.accountDepth = 0; this.accountDepth = 0;
this.token = encoding.ZERO_HASH; this.token = encoding.ZERO_HASH;
@ -90,10 +89,10 @@ class Wallet extends EventEmitter {
'Must create wallet with hd private key.'); 'Must create wallet with hd private key.');
} else { } else {
mnemonic = new Mnemonic(options.mnemonic); mnemonic = new Mnemonic(options.mnemonic);
key = HD.fromMnemonic(mnemonic); key = HD.fromMnemonic(mnemonic, options.password);
} }
this.master.fromKey(key, mnemonic, this.network); this.master.fromKey(key, mnemonic);
if (options.wid != null) { if (options.wid != null) {
assert((options.wid >>> 0) === options.wid); assert((options.wid >>> 0) === options.wid);
@ -105,11 +104,6 @@ class Wallet extends EventEmitter {
id = options.id; id = options.id;
} }
if (options.initialized != null) {
assert(typeof options.initialized === 'boolean');
this.initialized = options.initialized;
}
if (options.watchOnly != null) { if (options.watchOnly != null) {
assert(typeof options.watchOnly === 'boolean'); assert(typeof options.watchOnly === 'boolean');
this.watchOnly = options.watchOnly; this.watchOnly = options.watchOnly;
@ -165,9 +159,6 @@ class Wallet extends EventEmitter {
async init(options) { async init(options) {
const passphrase = options.passphrase; const passphrase = options.passphrase;
assert(!this.initialized);
this.initialized = true;
if (passphrase) if (passphrase)
await this.master.encrypt(passphrase); await this.master.encrypt(passphrase);
@ -185,8 +176,6 @@ class Wallet extends EventEmitter {
*/ */
async open() { async open() {
assert(this.initialized);
const account = await this.getAccount(0); const account = await this.getAccount(0);
if (!account) if (!account)
@ -1101,9 +1090,6 @@ class Wallet extends EventEmitter {
if (!options) if (!options)
options = {}; options = {};
if (!this.initialized)
throw new Error('Wallet is not initialized.');
if (this.watchOnly) if (this.watchOnly)
throw new Error('Cannot fund from watch-only wallet.'); throw new Error('Cannot fund from watch-only wallet.');
@ -2233,7 +2219,6 @@ class Wallet extends EventEmitter {
wid: this.wid, wid: this.wid,
id: this.id, id: this.id,
network: this.network.type, network: this.network.type,
initialized: this.initialized,
accountDepth: this.accountDepth, accountDepth: this.accountDepth,
token: this.token.toString('hex'), token: this.token.toString('hex'),
tokenDepth: this.tokenDepth, tokenDepth: this.tokenDepth,
@ -2254,12 +2239,11 @@ class Wallet extends EventEmitter {
network: this.network.type, network: this.network.type,
wid: this.wid, wid: this.wid,
id: this.id, id: this.id,
initialized: this.initialized,
watchOnly: this.watchOnly, watchOnly: this.watchOnly,
accountDepth: this.accountDepth, accountDepth: this.accountDepth,
token: this.token.toString('hex'), token: this.token.toString('hex'),
tokenDepth: this.tokenDepth, tokenDepth: this.tokenDepth,
master: this.master.toJSON(unsafe), master: this.master.toJSON(this.network, unsafe),
balance: balance ? balance.toJSON(true) : null balance: balance ? balance.toJSON(true) : null
}; };
} }
@ -2271,9 +2255,9 @@ class Wallet extends EventEmitter {
getSize() { getSize() {
let size = 0; let size = 0;
size += 50; size += 45;
size += encoding.sizeVarString(this.id, 'ascii'); size += encoding.sizeVarString(this.id, 'ascii');
size += encoding.sizeVarlen(this.master.getSize()); size += this.master.getSize();
return size; return size;
} }
@ -2286,15 +2270,18 @@ class Wallet extends EventEmitter {
const size = this.getSize(); const size = this.getSize();
const bw = bio.write(size); const bw = bio.write(size);
bw.writeU32(this.network.magic); let flags = 0;
if (this.watchOnly)
flags |= 1;
bw.writeU32(this.wid); bw.writeU32(this.wid);
bw.writeVarString(this.id, 'ascii'); bw.writeVarString(this.id, 'ascii');
bw.writeU8(this.initialized ? 1 : 0); bw.writeU8(flags);
bw.writeU8(this.watchOnly ? 1 : 0);
bw.writeU32(this.accountDepth); bw.writeU32(this.accountDepth);
bw.writeBytes(this.token); bw.writeBytes(this.token);
bw.writeU32(this.tokenDepth); bw.writeU32(this.tokenDepth);
bw.writeVarBytes(this.master.toRaw()); this.master.toWriter(bw);
return bw.render(); return bw.render();
} }
@ -2307,18 +2294,17 @@ class Wallet extends EventEmitter {
fromRaw(data) { fromRaw(data) {
const br = bio.read(data); const br = bio.read(data);
const network = Network.fromMagic(br.readU32());
assert(network === this.network, 'Wallet network mismatch.');
this.wid = br.readU32(); this.wid = br.readU32();
this.id = br.readVarString('ascii'); this.id = br.readVarString('ascii');
this.initialized = br.readU8() === 1;
this.watchOnly = br.readU8() === 1; const flags = br.readU8();
this.watchOnly = (flags & 1) !== 0;
this.accountDepth = br.readU32(); this.accountDepth = br.readU32();
this.token = br.readBytes(32); this.token = br.readBytes(32);
this.tokenDepth = br.readU32(); this.tokenDepth = br.readU32();
this.master.fromRaw(br.readVarBytes(), this.network); this.master.fromReader(br);
return this; return this;
} }

View File

@ -106,6 +106,7 @@ async function updateTXDB() {
await updateTX(wid); await updateTX(wid);
await updateWalletBalance(wid); await updateWalletBalance(wid);
await updateAccountBalances(wid); await updateAccountBalances(wid);
await updateWallet(wid);
} }
} }
@ -277,6 +278,186 @@ async function updateAccountBalance(wid, acct) {
batch.put(c(pre, tlayout.r(acct)), serializeBalance(bal)); batch.put(c(pre, tlayout.r(acct)), serializeBalance(bal));
} }
async function updateWallet(wid) {
const raw = await db.get(layout.w(wid));
assert(raw);
const br = bio.read(raw, true);
br.readU32(); // Skip network.
const wid = br.readU32();
const id = br.readVarString('ascii');
const initialized = br.readU8() === 1;
const watchOnly = br.readU8() === 1;
const accountDepth = br.readU32();
const token = br.readBytes(32);
const tokenDepth = br.readU32();
// We want to get the key
// _out of_ varint serialization.
let key = br.readVarBytes();
const kr = bio.read(key, true);
// Unencrypted?
if (kr.readU8() === 0) {
const bw = bio.write();
bw.writeU8(0);
// Skip useless varint.
kr.readVarint();
// Skip HD key params.
kr.seek(13);
// Read/write chain code.
bw.writeBytes(kr.readBytes(32));
// Skip zero byte.
assert(kr.readU8() === 0);
// Read/write private key.
bw.writeBytes(kr.readBytes(32));
// Skip checksum.
kr.seek(4);
// Include mnemonic.
if (kr.readU8() === 1) {
bw.writeU8(1);
const bits = kr.readU16();
assert(bits % 32 === 0);
const lang = kr.readU8();
const entropy = kr.readBytes(bits / 8);
bw.writeU16(bits);
bw.writeU8(lang);
bw.writeBytes(entropy);
} else {
bw.writeU8(0);
}
key = bw.render();
}
let flags = 0;
if (watchOnly)
flags |= 1;
// Concatenate wallet with key.
const bw = bio.write();
bw.writeU32(wid);
bw.writeVarString(id, 'ascii');
bw.writeU8(flags);
bw.writeU32(accountDepth);
bw.writeBytes(token);
bw.writeU32(tokenDepth);
bw.writeBytes(key);
batch.put(layout.w(wid), bw.render());
for (let acct = 0; acct < accountDepth; acct++)
await updateAccount(wid, acct);
}
async function updateAccount(wid, acct) {
const raw = await db.get(layout.a(wid, acct));
assert(raw);
const br = bio.read(raw, true);
const name = br.readVarString('ascii');
const initialized = br.readU8() === 1;
const witness = br.readU8() === 1;
const type = br.readU8();
const m = br.readU8();
const n = br.readU8();
const accountIndex = br.readU32();
const receiveDepth = br.readU32();
const changeDepth = br.readU32();
const nestedDepth = br.readU32();
const lookahead = br.readU8();
const accountKey = {
network: br.readU32BE(),
depth: br.readU8(),
parentFingerPrint: br.readU32BE(),
childIndex: br.readU32BE(),
chainCode: br.readBytes(32),
publicKey: br.readBytes(33),
checksum: br.readU32()
};
const count = br.readU8();
const keys = [];
for (let i = 0; i < count; i++) {
const key = {
network: br.readU32BE(),
depth: br.readU8(),
parentFingerPrint: br.readU32BE(),
childIndex: br.readU32BE(),
chainCode: br.readBytes(32),
publicKey: br.readBytes(33),
checksum: br.readU32()
};
keys.push(key);
}
const bw = bio.write();
let flags = 0;
if (initialized)
flags |= 1;
if (witness)
flags |= 2;
bw.writeU32(accountIndex);
bw.writeVarString(name, 'ascii');
bw.writeU8(flags);
bw.writeU8(type);
bw.writeU8(m);
bw.writeU8(n);
bw.writeU32(receiveDepth);
bw.writeU32(changeDepth);
bw.writeU32(nestedDepth);
bw.writeU8(lookahead);
bw.writeU8(accountKey.depth);
bw.writeU32BE(accountKey.parentFingerPrint);
bw.writeU32BE(accountKey.childIndex);
bw.writeBytes(accountKey.chainCode);
bw.writeBytes(accountKey.publicKey);
bw.writeU8(keys.length);
for (const key of keys) {
bw.writeU8(key.depth);
bw.writeU32BE(key.parentFingerPrint);
bw.writeU32BE(key.childIndex);
bw.writeBytes(key.chainCode);
bw.writeBytes(key.publicKey);
}
batch.put(layout.a(wid, acct), bw.render());
}
async function updatePaths() {
const iter = db.iterator({
gte: layout.p(encoding.NULL_HASH),
lte: layout.p(encoding.HIGH_HASH),
keys: true,
values: true
});
await iter.each((key, value) => {
const br = bio.read(value);
});
}
/* /*
* Old Records * Old Records
*/ */

View File

@ -27,30 +27,28 @@ describe('Mnemonic', function() {
it(`should create a ${language} mnemonic from entropy (${i})`, () => { it(`should create a ${language} mnemonic from entropy (${i})`, () => {
const mnemonic = new Mnemonic({ const mnemonic = new Mnemonic({
language, language,
entropy, entropy
passphrase
}); });
assert.strictEqual(mnemonic.getPhrase(), phrase); assert.strictEqual(mnemonic.getPhrase(), phrase);
assert.bufferEqual(mnemonic.getEntropy(), entropy); assert.bufferEqual(mnemonic.getEntropy(), entropy);
assert.bufferEqual(mnemonic.toSeed(), seed); assert.bufferEqual(mnemonic.toSeed(passphrase), seed);
const key = HDPrivateKey.fromMnemonic(mnemonic); const key = HDPrivateKey.fromMnemonic(mnemonic, passphrase);
assert.strictEqual(key.toBase58('main'), xpriv); assert.strictEqual(key.toBase58('main'), xpriv);
}); });
it(`should create a ${language} mnemonic from phrase (${i})`, () => { it(`should create a ${language} mnemonic from phrase (${i})`, () => {
const mnemonic = new Mnemonic({ const mnemonic = new Mnemonic({
language, language,
phrase, phrase
passphrase
}); });
assert.strictEqual(mnemonic.getPhrase(), phrase); assert.strictEqual(mnemonic.getPhrase(), phrase);
assert.bufferEqual(mnemonic.getEntropy(), entropy); assert.bufferEqual(mnemonic.getEntropy(), entropy);
assert.bufferEqual(mnemonic.toSeed(), seed); assert.bufferEqual(mnemonic.toSeed(passphrase), seed);
const key = HDPrivateKey.fromMnemonic(mnemonic); const key = HDPrivateKey.fromMnemonic(mnemonic, passphrase);
assert.strictEqual(key.toBase58('main'), xpriv); assert.strictEqual(key.toBase58('main'), xpriv);
}); });