wallet: more migrations.
This commit is contained in:
parent
0055c82f22
commit
e2ef35b24b
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user