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.
* @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;
}

View File

@ -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);
}
/**

View File

@ -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
*/

View File

@ -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

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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
*/

View File

@ -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);
});