diff --git a/lib/hd/mnemonic.js b/lib/hd/mnemonic.js index ab319148..98c077bd 100644 --- a/lib/hd/mnemonic.js +++ b/lib/hd/mnemonic.js @@ -41,8 +41,6 @@ class Mnemonic { * if not present. * @param {String?} options.phrase - Mnemonic phrase (will * be generated if not present). - * @param {String?} options.passphrase - Optional salt for - * key stretching (empty string if not present). * @param {String?} options.language - Language. */ @@ -51,7 +49,6 @@ class Mnemonic { this.language = 'english'; this.entropy = null; this.phrase = null; - this.passphrase = ''; if (options) this.fromOptions(options); @@ -81,11 +78,6 @@ class Mnemonic { this.language = options.language; } - if (options.passphrase) { - assert(typeof options.passphrase === 'string'); - this.passphrase = options.passphrase; - } - if (options.phrase) { this.fromPhrase(options.phrase); return this; @@ -121,7 +113,6 @@ class Mnemonic { this.entropy = null; } this.phrase = null; - this.passphrase = ''; } /** @@ -132,12 +123,10 @@ class Mnemonic { toSeed(passphrase) { if (!passphrase) - passphrase = this.passphrase; - - this.passphrase = passphrase; + passphrase = ''; const phrase = nfkd(this.getPhrase()); - const passwd = nfkd('mnemonic' + passphrase); + const passwd = nfkd(`mnemonic${passphrase}`); return pbkdf2.derive(sha512, Buffer.from(phrase, 'utf8'), @@ -375,8 +364,7 @@ class Mnemonic { bits: this.bits, language: this.language, entropy: this.getEntropy().toString('hex'), - phrase: this.getPhrase(), - passphrase: this.passphrase + phrase: this.getPhrase() }; } @@ -392,7 +380,6 @@ class Mnemonic { assert(typeof json.language === 'string'); assert(typeof json.entropy === 'string'); assert(typeof json.phrase === 'string'); - assert(typeof json.passphrase === 'string'); assert(json.bits >= common.MIN_ENTROPY); assert(json.bits <= common.MAX_ENTROPY); assert(json.bits % 32 === 0); @@ -402,7 +389,6 @@ class Mnemonic { this.language = json.language; this.entropy = Buffer.from(json.entropy, 'hex'); this.phrase = json.phrase; - this.passphrase = json.passphrase; return this; } @@ -426,8 +412,6 @@ class Mnemonic { let size = 0; size += 3; size += this.getEntropy().length; - size += encoding.sizeVarString(this.getPhrase(), 'utf8'); - size += encoding.sizeVarString(this.passphrase, 'utf8'); return size; } @@ -444,8 +428,6 @@ class Mnemonic { bw.writeU16(this.bits); bw.writeU8(lang); bw.writeBytes(this.getEntropy()); - bw.writeVarString(this.getPhrase(), 'utf8'); - bw.writeVarString(this.passphrase, 'utf8'); return bw; } @@ -479,8 +461,6 @@ class Mnemonic { this.bits = bits; this.language = language; this.entropy = br.readBytes(bits / 8); - this.phrase = br.readVarString('utf8'); - this.passphrase = br.readVarString('utf8'); return this; } diff --git a/lib/hd/private.js b/lib/hd/private.js index cfa9ebe5..3f7ad833 100644 --- a/lib/hd/private.js +++ b/lib/hd/private.js @@ -460,21 +460,23 @@ class HDPrivateKey { * Inject properties from a mnemonic. * @private * @param {Mnemonic} mnemonic + * @param {String?} passphrase */ - fromMnemonic(mnemonic) { + fromMnemonic(mnemonic, passphrase) { assert(mnemonic instanceof Mnemonic); - return this.fromSeed(mnemonic.toSeed()); + return this.fromSeed(mnemonic.toSeed(passphrase)); } /** * Instantiate an hd private key from a mnemonic. * @param {Mnemonic} mnemonic + * @param {String?} passphrase * @returns {HDPrivateKey} */ - static fromMnemonic(mnemonic) { - return new this().fromMnemonic(mnemonic); + static fromMnemonic(mnemonic, passphrase) { + return new this().fromMnemonic(mnemonic, passphrase); } /** diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 93a9ce72..91ae80b2 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -13,7 +13,7 @@ const Path = require('./path'); const common = require('./common'); const Script = require('../script/script'); const WalletKey = require('./walletkey'); -const HD = require('../hd/hd'); +const {HDPublicKey} = require('../hd/hd'); const {encoding} = bio; /** @@ -40,6 +40,7 @@ class Account { this.wid = 0; this.id = null; + this.accountIndex = 0; this.name = null; this.initialized = false; this.witness = wdb.options.witness === true; @@ -47,7 +48,6 @@ class Account { this.type = Account.types.PUBKEYHASH; this.m = 1; this.n = 1; - this.accountIndex = 0; this.receiveDepth = 0; this.changeDepth = 0; this.nestedDepth = 0; @@ -69,13 +69,19 @@ class Account { assert(options, 'Options are required.'); assert((options.wid >>> 0) === options.wid); 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, 'Account index is required.'); this.wid = options.wid; this.id = options.id; + if (options.accountIndex != null) { + assert((options.accountIndex >>> 0) === options.accountIndex); + this.accountIndex = options.accountIndex; + } + if (options.name != null) { assert(common.isName(options.name), 'Bad account name.'); this.name = options.name; @@ -117,11 +123,6 @@ class Account { this.n = options.n; } - if (options.accountIndex != null) { - assert((options.accountIndex >>> 0) === options.accountIndex); - this.accountIndex = options.accountIndex; - } - if (options.receiveDepth != null) { assert((options.receiveDepth >>> 0) === options.receiveDepth); this.receiveDepth = options.receiveDepth; @@ -210,9 +211,9 @@ class Account { pushKey(key) { 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.'); if (!key.isAccount()) @@ -247,9 +248,9 @@ class Account { spliceKey(key) { 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.'); if (!key.isAccount()) @@ -455,7 +456,7 @@ class Account { /** * 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 * @returns {WalletKey} */ @@ -842,8 +843,8 @@ class Account { getSize() { let size = 0; size += encoding.sizeVarString(this.name, 'ascii'); - size += 105; - size += this.keys.length * 82; + size += 96; + size += this.keys.length * 74; return size; } @@ -856,22 +857,29 @@ class Account { const size = this.getSize(); 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.writeU8(this.initialized ? 1 : 0); - bw.writeU8(this.witness ? 1 : 0); + bw.writeU8(flags); bw.writeU8(this.type); bw.writeU8(this.m); bw.writeU8(this.n); - bw.writeU32(this.accountIndex); bw.writeU32(this.receiveDepth); bw.writeU32(this.changeDepth); bw.writeU32(this.nestedDepth); bw.writeU8(this.lookahead); - bw.writeBytes(this.accountKey.toRaw(this.network)); + writeKey(this.accountKey, bw); bw.writeU8(this.keys.length); for (const key of this.keys) - bw.writeBytes(key.toRaw(this.network)); + writeKey(key, bw); return bw.render(); } @@ -886,26 +894,29 @@ class Account { fromRaw(data) { const br = bio.read(data); + this.accountIndex = br.readU32(); 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.m = br.readU8(); this.n = br.readU8(); - this.accountIndex = br.readU32(); this.receiveDepth = br.readU32(); this.changeDepth = br.readU32(); this.nestedDepth = br.readU32(); 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(); for (let i = 0; i < count; i++) { - const key = HD.PublicKey.fromRaw(br.readBytes(82), this.network); - this.pushKey(key); + const key = readKey(br); + binary.insert(this.keys, key, cmp, true); } return this; @@ -954,7 +965,7 @@ Account.typesByVal = [ 'multisig' ]; -/* +/** * Default address lookahead. * @const {Number} */ @@ -969,6 +980,24 @@ function cmp(a, 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 */ diff --git a/lib/wallet/http.js b/lib/wallet/http.js index 7924d6bd..6d671d26 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -211,7 +211,7 @@ class HTTP extends Server { // Get wallet master key 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 diff --git a/lib/wallet/masterkey.js b/lib/wallet/masterkey.js index 2d75a1ef..dee2ceb5 100644 --- a/lib/wallet/masterkey.js +++ b/lib/wallet/masterkey.js @@ -13,13 +13,13 @@ const random = require('bcrypto/lib/random'); const cleanse = require('bcrypto/lib/cleanse'); const aes = require('bcrypto/lib/aes'); 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 scrypt = require('bcrypto/lib/scrypt'); -const Network = require('../protocol/network'); const util = require('../utils/util'); -const HD = require('../hd/hd'); +const {HDPrivateKey, Mnemonic} = require('../hd/hd'); const {encoding} = bio; -const {Mnemonic} = HD; /** * Master Key @@ -43,14 +43,13 @@ class MasterKey { this.mnemonic = null; this.alg = MasterKey.alg.PBKDF2; - this.N = 50000; + this.n = 50000; this.r = 0; this.p = 0; this.aesKey = null; this.timer = null; this.until = 0; - this._onTimeout = this.lock.bind(this); this.locker = new Lock(); if (options) @@ -66,9 +65,6 @@ class MasterKey { fromOptions(options) { assert(options); - if (options.network != null) - this.network = Network.get(options.network); - if (options.encrypted != null) { assert(typeof options.encrypted === 'boolean'); this.encrypted = options.encrypted; @@ -85,7 +81,7 @@ class MasterKey { } if (options.key) { - assert(HD.isPrivate(options.key)); + assert(HDPrivateKey.isHDPrivateKey(options.key)); this.key = options.key; } @@ -107,12 +103,12 @@ class MasterKey { if (options.rounds != null) { assert((options.rounds >>> 0) === options.rounds); - this.N = options.rounds; + this.n = options.rounds; } - if (options.N != null) { - assert((options.N >>> 0) === options.N); - this.N = options.N; + if (options.n != null) { + assert((options.n >>> 0) === options.n); + this.n = options.n; } if (options.r != null) { @@ -192,7 +188,7 @@ class MasterKey { /** * Start the destroy timer. * @private - * @param {Number} [timeout=60000] timeout in ms. + * @param {Number} [timeout=60] timeout in seconds. */ start(timeout) { @@ -204,8 +200,10 @@ class MasterKey { if (timeout === -1) return; + assert((timeout >>> 0) === 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) { const salt = MasterKey.SALT; - const N = this.N; + const n = this.n; const r = this.r; const p = this.p; @@ -238,9 +236,9 @@ class MasterKey { switch (this.alg) { 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: - return await scrypt.deriveAsync(passwd, salt, N, r, p, 32); + return scrypt.deriveAsync(passwd, salt, n, r, p, 32); default: throw new Error(`Unknown algorithm: ${this.alg}.`); } @@ -437,7 +435,7 @@ class MasterKey { keySize() { let size = 0; - size += this.key.getSize(); + size += 64; size += 1; if (this.mnemonic) @@ -454,7 +452,8 @@ class MasterKey { writeKey() { 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) { bw.writeU8(1); @@ -474,7 +473,19 @@ class MasterKey { readKey(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) this.mnemonic = Mnemonic.fromReader(br); @@ -499,7 +510,7 @@ class MasterKey { } size += 1; - size += encoding.sizeVarlen(this.keySize()); + size += this.keySize(); return size; } @@ -510,29 +521,24 @@ class MasterKey { * @returns {Buffer} */ - toRaw() { - const bw = bio.write(this.getSize()); - + toWriter(bw) { if (this.encrypted) { bw.writeU8(1); bw.writeVarBytes(this.iv); bw.writeVarBytes(this.ciphertext); bw.writeU8(this.alg); - bw.writeU32(this.N); + bw.writeU32(this.n); bw.writeU32(this.r); bw.writeU32(this.p); - return bw.render(); + return bw; } bw.writeU8(0); - // NOTE: useless varint - const size = this.keySize(); - bw.writeVarint(size); - - bw.writeBytes(this.key.toRaw(this.network)); + bw.writeBytes(this.key.chainCode); + bw.writeBytes(this.key.privateKey); if (this.mnemonic) { bw.writeU8(1); @@ -541,7 +547,18 @@ class MasterKey { 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 */ - fromRaw(raw, network) { - const br = bio.read(raw); - - this.network = Network.get(network); + fromReader(br) { this.encrypted = br.readU8() === 1; if (this.encrypted) { @@ -564,17 +578,17 @@ class MasterKey { assert(MasterKey.algByVal[this.alg]); - this.N = br.readU32(); + this.n = br.readU32(); this.r = br.readU32(); this.p = br.readU32(); return this; } - // NOTE: useless varint - br.readVarint(); - - this.key = HD.PrivateKey.fromRaw(br.readBytes(82), this.network); + this.key = new HDPrivateKey(); + 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) this.mnemonic = Mnemonic.fromReader(br); @@ -587,8 +601,27 @@ class MasterKey { * @returns {MasterKey} */ - static fromRaw(raw, network) { - return new this().fromRaw(raw, network); + static fromReader(br) { + 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 */ - fromKey(key, mnemonic, network) { + fromKey(key, mnemonic) { this.encrypted = false; this.iv = null; this.ciphertext = null; this.key = key; this.mnemonic = mnemonic || null; - this.network = Network.get(network); return this; } @@ -615,18 +647,19 @@ class MasterKey { * @returns {MasterKey} */ - static fromKey(key, mnemonic, network) { - return new this().fromKey(key, mnemonic, network); + static fromKey(key, mnemonic) { + return new this().fromKey(key, mnemonic); } /** * Convert master key to a jsonifiable object. + * @param {Network?} network * @param {Boolean?} unsafe - Whether to include * the key data in the JSON. * @returns {Object} */ - toJSON(unsafe) { + toJSON(network, unsafe) { if (this.encrypted) { return { encrypted: true, @@ -634,7 +667,7 @@ class MasterKey { iv: this.iv.toString('hex'), ciphertext: unsafe ? this.ciphertext.toString('hex') : undefined, algorithm: MasterKey.algByVal[this.alg].toLowerCase(), - N: this.N, + n: this.n, r: this.r, p: this.p }; @@ -642,7 +675,7 @@ class MasterKey { return { 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 }; } @@ -653,10 +686,10 @@ class MasterKey { */ inspect() { - const json = this.toJSON(true); + const json = this.toJSON(null, true); if (this.key) - json.key = this.key.toJSON(this.network); + json.key = this.key.toJSON(); if (this.mnemonic) json.mnemonic = this.mnemonic.toJSON(); @@ -705,6 +738,22 @@ MasterKey.algByVal = [ '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 */ diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 42101eec..ab53d67c 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -59,7 +59,6 @@ class Wallet extends EventEmitter { this.wid = 0; this.id = null; - this.initialized = false; this.watchOnly = false; this.accountDepth = 0; this.token = encoding.ZERO_HASH; @@ -90,10 +89,10 @@ class Wallet extends EventEmitter { 'Must create wallet with hd private key.'); } else { 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) { assert((options.wid >>> 0) === options.wid); @@ -105,11 +104,6 @@ class Wallet extends EventEmitter { id = options.id; } - if (options.initialized != null) { - assert(typeof options.initialized === 'boolean'); - this.initialized = options.initialized; - } - if (options.watchOnly != null) { assert(typeof options.watchOnly === 'boolean'); this.watchOnly = options.watchOnly; @@ -165,9 +159,6 @@ class Wallet extends EventEmitter { async init(options) { const passphrase = options.passphrase; - assert(!this.initialized); - this.initialized = true; - if (passphrase) await this.master.encrypt(passphrase); @@ -185,8 +176,6 @@ class Wallet extends EventEmitter { */ async open() { - assert(this.initialized); - const account = await this.getAccount(0); if (!account) @@ -1101,9 +1090,6 @@ class Wallet extends EventEmitter { if (!options) options = {}; - if (!this.initialized) - throw new Error('Wallet is not initialized.'); - if (this.watchOnly) throw new Error('Cannot fund from watch-only wallet.'); @@ -2233,7 +2219,6 @@ class Wallet extends EventEmitter { wid: this.wid, id: this.id, network: this.network.type, - initialized: this.initialized, accountDepth: this.accountDepth, token: this.token.toString('hex'), tokenDepth: this.tokenDepth, @@ -2254,12 +2239,11 @@ class Wallet extends EventEmitter { network: this.network.type, wid: this.wid, id: this.id, - initialized: this.initialized, watchOnly: this.watchOnly, accountDepth: this.accountDepth, token: this.token.toString('hex'), tokenDepth: this.tokenDepth, - master: this.master.toJSON(unsafe), + master: this.master.toJSON(this.network, unsafe), balance: balance ? balance.toJSON(true) : null }; } @@ -2271,9 +2255,9 @@ class Wallet extends EventEmitter { getSize() { let size = 0; - size += 50; + size += 45; size += encoding.sizeVarString(this.id, 'ascii'); - size += encoding.sizeVarlen(this.master.getSize()); + size += this.master.getSize(); return size; } @@ -2286,15 +2270,18 @@ class Wallet extends EventEmitter { const size = this.getSize(); const bw = bio.write(size); - bw.writeU32(this.network.magic); + let flags = 0; + + if (this.watchOnly) + flags |= 1; + bw.writeU32(this.wid); bw.writeVarString(this.id, 'ascii'); - bw.writeU8(this.initialized ? 1 : 0); - bw.writeU8(this.watchOnly ? 1 : 0); + bw.writeU8(flags); bw.writeU32(this.accountDepth); bw.writeBytes(this.token); bw.writeU32(this.tokenDepth); - bw.writeVarBytes(this.master.toRaw()); + this.master.toWriter(bw); return bw.render(); } @@ -2307,18 +2294,17 @@ class Wallet extends EventEmitter { fromRaw(data) { const br = bio.read(data); - const network = Network.fromMagic(br.readU32()); - - assert(network === this.network, 'Wallet network mismatch.'); this.wid = br.readU32(); 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.token = br.readBytes(32); this.tokenDepth = br.readU32(); - this.master.fromRaw(br.readVarBytes(), this.network); + this.master.fromReader(br); return this; } diff --git a/migrate/walletdb6to7.js b/migrate/walletdb6to7.js index f3c2f545..9f9ac090 100644 --- a/migrate/walletdb6to7.js +++ b/migrate/walletdb6to7.js @@ -106,6 +106,7 @@ async function updateTXDB() { await updateTX(wid); await updateWalletBalance(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)); } +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 */ diff --git a/test/mnemonic-test.js b/test/mnemonic-test.js index 2ecc77a4..2b3eb726 100644 --- a/test/mnemonic-test.js +++ b/test/mnemonic-test.js @@ -27,30 +27,28 @@ describe('Mnemonic', function() { it(`should create a ${language} mnemonic from entropy (${i})`, () => { const mnemonic = new Mnemonic({ language, - entropy, - passphrase + entropy }); assert.strictEqual(mnemonic.getPhrase(), phrase); 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); }); it(`should create a ${language} mnemonic from phrase (${i})`, () => { const mnemonic = new Mnemonic({ language, - phrase, - passphrase + phrase }); assert.strictEqual(mnemonic.getPhrase(), phrase); 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); });