walletdb: path refactor.
This commit is contained in:
parent
77e1af4faf
commit
2544e5310a
@ -13,6 +13,9 @@ var co = spawn.co;
|
||||
var assert = utils.assert;
|
||||
var BufferReader = require('../utils/reader');
|
||||
var BufferWriter = require('../utils/writer');
|
||||
var Path = require('./path');
|
||||
var Script = require('../script/script');
|
||||
var KeyRing = require('../primitives/keyring');
|
||||
|
||||
/**
|
||||
* Represents a BIP44 Account belonging to a {@link Wallet}.
|
||||
@ -449,30 +452,31 @@ Account.prototype.deriveChange = function deriveChange(index, master) {
|
||||
*/
|
||||
|
||||
Account.prototype.derivePath = function derivePath(path, master) {
|
||||
var ring, raw;
|
||||
var data = path.data;
|
||||
var ring;
|
||||
|
||||
// Imported key.
|
||||
if (path.index === -1) {
|
||||
assert(path.imported);
|
||||
assert(this.type === Account.types.PUBKEYHASH);
|
||||
switch (path.keyType) {
|
||||
case Path.types.HD:
|
||||
return this.deriveAddress(path.change, path.index, master);
|
||||
case Path.types.KEY:
|
||||
assert(this.type === Account.types.PUBKEYHASH);
|
||||
|
||||
raw = path.imported;
|
||||
if (path.encrypted) {
|
||||
data = master.decipher(data, path.hash);
|
||||
if (!data)
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.encrypted)
|
||||
raw = master.decipher(raw, path.hash);
|
||||
ring = KeyRing.fromRaw(data, this.network);
|
||||
ring.witness = this.witness;
|
||||
ring.path = path;
|
||||
|
||||
if (!raw)
|
||||
return ring;
|
||||
case Path.types.ADDRESS:
|
||||
return;
|
||||
|
||||
ring = bcoin.keyring.fromRaw(raw, this.network);
|
||||
ring.path = path;
|
||||
|
||||
return ring;
|
||||
default:
|
||||
assert(false, 'Bad key type.');
|
||||
}
|
||||
|
||||
ring = this.deriveAddress(path.change, path.index, master);
|
||||
|
||||
return ring;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -495,7 +499,7 @@ Account.prototype.deriveAddress = function deriveAddress(change, index, master)
|
||||
key = this.accountKey.derive(change).derive(index);
|
||||
}
|
||||
|
||||
ring = bcoin.keyring.fromPublic(key.publicKey, this.network);
|
||||
ring = KeyRing.fromPublic(key.publicKey, this.network);
|
||||
ring.witness = this.witness;
|
||||
|
||||
switch (this.type) {
|
||||
@ -510,7 +514,7 @@ Account.prototype.deriveAddress = function deriveAddress(change, index, master)
|
||||
keys.push(shared.publicKey);
|
||||
}
|
||||
|
||||
ring.script = bcoin.script.fromMultisig(this.m, this.n, keys);
|
||||
ring.script = Script.fromMultisig(this.m, this.n, keys);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -518,7 +522,7 @@ Account.prototype.deriveAddress = function deriveAddress(change, index, master)
|
||||
if (key.privateKey)
|
||||
ring.privateKey = key.privateKey;
|
||||
|
||||
ring.path = bcoin.path.fromAccount(this, ring, change, index);
|
||||
ring.path = Path.fromHD(this, ring, change, index);
|
||||
|
||||
return ring;
|
||||
};
|
||||
@ -543,6 +547,16 @@ Account.prototype.saveAddress = function saveAddress(rings) {
|
||||
return this.db.saveAddress(this.wid, rings);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save paths to path map.
|
||||
* @param {Path[]} rings
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Account.prototype.savePath = function savePath(paths) {
|
||||
return this.db.savePath(this.wid, paths);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set change and receiving depth (depth is the index of the _next_ address).
|
||||
* Allocate all addresses up to depth. Note that this also allocates
|
||||
|
||||
@ -12,6 +12,7 @@ var assert = utils.assert;
|
||||
var constants = bcoin.constants;
|
||||
var BufferReader = require('../utils/reader');
|
||||
var BufferWriter = require('../utils/writer');
|
||||
var Address = require('../primitives/address');
|
||||
|
||||
/**
|
||||
* Path
|
||||
@ -34,9 +35,10 @@ function Path() {
|
||||
this.account = 0;
|
||||
this.change = -1;
|
||||
this.index = -1;
|
||||
this.keyType = -1;
|
||||
|
||||
this.encrypted = false;
|
||||
this.imported = null;
|
||||
this.data = null;
|
||||
|
||||
// Currently unused.
|
||||
this.type = bcoin.script.types.PUBKEYHASH;
|
||||
@ -47,6 +49,18 @@ function Path() {
|
||||
this.hash = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path types.
|
||||
* @enum {Number}
|
||||
* @default
|
||||
*/
|
||||
|
||||
Path.types = {
|
||||
HD: 0,
|
||||
KEY: 1,
|
||||
ADDRESS: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone the path object.
|
||||
* @returns {Path}
|
||||
@ -60,9 +74,10 @@ Path.prototype.clone = function clone() {
|
||||
path.account = this.account;
|
||||
path.change = this.change;
|
||||
path.index = this.index;
|
||||
path.keyType = this.keyType;
|
||||
|
||||
path.encrypted = this.encrypted;
|
||||
path.imported = this.imported;
|
||||
path.data = this.data;
|
||||
|
||||
path.type = this.type;
|
||||
path.version = this.version;
|
||||
@ -84,17 +99,21 @@ Path.prototype.fromRaw = function fromRaw(data) {
|
||||
|
||||
this.wid = p.readU32();
|
||||
this.account = p.readU32();
|
||||
this.change = -1;
|
||||
this.index = -1;
|
||||
this.keyType = p.readU8();
|
||||
|
||||
switch (p.readU8()) {
|
||||
case 0:
|
||||
switch (this.keyType) {
|
||||
case Path.types.HD:
|
||||
this.change = p.readU32();
|
||||
this.index = p.readU32();
|
||||
break;
|
||||
case 1:
|
||||
case Path.types.KEY:
|
||||
this.encrypted = p.readU8() === 1;
|
||||
this.imported = p.readVarBytes();
|
||||
this.change = -1;
|
||||
this.index = -1;
|
||||
this.data = p.readVarBytes();
|
||||
break;
|
||||
case Path.types.ADDRESS:
|
||||
// Hash will be passed in by caller.
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@ -128,16 +147,28 @@ Path.prototype.toRaw = function toRaw(writer) {
|
||||
p.writeU32(this.wid);
|
||||
p.writeU32(this.account);
|
||||
|
||||
if (this.index !== -1) {
|
||||
assert(!this.imported);
|
||||
p.writeU8(0);
|
||||
p.writeU32(this.change);
|
||||
p.writeU32(this.index);
|
||||
} else {
|
||||
assert(this.imported);
|
||||
p.writeU8(1);
|
||||
p.writeU8(this.encrypted ? 1 : 0);
|
||||
p.writeVarBytes(this.imported);
|
||||
p.writeU8(this.keyType);
|
||||
|
||||
switch (this.keyType) {
|
||||
case Path.types.HD:
|
||||
assert(!this.data);
|
||||
assert(this.index !== -1);
|
||||
p.writeU32(this.change);
|
||||
p.writeU32(this.index);
|
||||
break;
|
||||
case Path.types.KEY:
|
||||
assert(this.data);
|
||||
assert(this.index === -1);
|
||||
p.writeU8(this.encrypted ? 1 : 0);
|
||||
p.writeVarBytes(this.data);
|
||||
break;
|
||||
case Path.types.ADDRESS:
|
||||
assert(!this.data);
|
||||
assert(this.index === -1);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
p.write8(this.version);
|
||||
@ -150,22 +181,22 @@ Path.prototype.toRaw = function toRaw(writer) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from account.
|
||||
* Inject properties from hd account.
|
||||
* @private
|
||||
* @param {WalletID} wid
|
||||
* @param {Account} account
|
||||
* @param {KeyRing} ring
|
||||
* @param {Number} change
|
||||
* @param {Number} index
|
||||
*/
|
||||
|
||||
Path.prototype.fromAccount = function fromAccount(account, ring, change, index) {
|
||||
Path.prototype.fromHD = function fromHD(account, ring, change, index) {
|
||||
this.wid = account.wid;
|
||||
this.name = account.name;
|
||||
this.account = account.accountIndex;
|
||||
this.change = change;
|
||||
this.index = index;
|
||||
|
||||
if (change != null)
|
||||
this.change = change;
|
||||
|
||||
if (index != null)
|
||||
this.index = index;
|
||||
this.keyType = Path.types.HD;
|
||||
|
||||
this.version = ring.witness ? 0 : -1;
|
||||
this.type = ring.getType();
|
||||
@ -176,15 +207,78 @@ Path.prototype.fromAccount = function fromAccount(account, ring, change, index)
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate path from hd account and keyring.
|
||||
* @param {Account} account
|
||||
* @param {KeyRing} ring
|
||||
* @param {Number} change
|
||||
* @param {Number} index
|
||||
* @returns {Path}
|
||||
*/
|
||||
|
||||
Path.fromHD = function fromHD(account, ring, change, index) {
|
||||
return new Path().fromHD(account, ring, change, index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from keyring.
|
||||
* @private
|
||||
* @param {Account} account
|
||||
* @param {KeyRing} ring
|
||||
*/
|
||||
|
||||
Path.prototype.fromKey = function fromKey(account, ring) {
|
||||
this.wid = account.wid;
|
||||
this.name = account.name;
|
||||
this.account = account.accountIndex;
|
||||
this.keyType = Path.types.KEY;
|
||||
this.data = ring.toRaw();
|
||||
this.version = ring.witness ? 0 : -1;
|
||||
this.type = ring.getType();
|
||||
this.id = account.id;
|
||||
this.hash = ring.getHash('hex');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate path from keyring.
|
||||
* @param {WalletID} wid
|
||||
* @param {Account} account
|
||||
* @param {KeyRing} ring
|
||||
* @returns {Path}
|
||||
*/
|
||||
|
||||
Path.fromAccount = function fromAccount(account, ring, change, index) {
|
||||
return new Path().fromAccount(account, ring, change, index);
|
||||
Path.fromKey = function fromKey(account, ring) {
|
||||
return new Path().fromKey(account, ring);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from address.
|
||||
* @private
|
||||
* @param {Account} account
|
||||
* @param {Address} address
|
||||
*/
|
||||
|
||||
Path.prototype.fromAddress = function fromAddress(account, address) {
|
||||
this.wid = account.wid;
|
||||
this.name = account.name;
|
||||
this.account = account.accountIndex;
|
||||
this.keyType = Path.types.ADDRESS;
|
||||
this.version = address.version;
|
||||
this.type = address.type;
|
||||
this.id = account.id;
|
||||
this.hash = address.getHash('hex');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate path from address.
|
||||
* @param {Account} account
|
||||
* @param {Address} address
|
||||
* @returns {Path}
|
||||
*/
|
||||
|
||||
Path.fromAddress = function fromAddress(account, address) {
|
||||
return new Path().fromAddress(account, address);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -193,6 +287,9 @@ Path.fromAccount = function fromAccount(account, ring, change, index) {
|
||||
*/
|
||||
|
||||
Path.prototype.toPath = function toPath() {
|
||||
if (this.keyType !== Path.types.HD)
|
||||
return null;
|
||||
|
||||
return 'm/' + this.account
|
||||
+ '\'/' + this.change
|
||||
+ '/' + this.index;
|
||||
@ -204,7 +301,7 @@ Path.prototype.toPath = function toPath() {
|
||||
*/
|
||||
|
||||
Path.prototype.toAddress = function toAddress(network) {
|
||||
return bcoin.address.fromHash(this.hash, this.type, this.version, network);
|
||||
return Address.fromHash(this.hash, this.type, this.version, network);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -220,39 +317,6 @@ Path.prototype.toJSON = function toJSON() {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from json object.
|
||||
* @private
|
||||
* @param {Object} json
|
||||
*/
|
||||
|
||||
Path.prototype.fromJSON = function fromJSON(json) {
|
||||
var indexes = bcoin.hd.parsePath(json.path, constants.hd.MAX_INDEX);
|
||||
|
||||
assert(indexes.length === 3);
|
||||
assert(indexes[0] >= constants.hd.HARDENED);
|
||||
indexes[0] -= constants.hd.HARDENED;
|
||||
|
||||
this.wid = json.wid;
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.account = indexes[0];
|
||||
this.change = indexes[1];
|
||||
this.index = indexes[2];
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate path from json object.
|
||||
* @param {Object} json
|
||||
* @returns {Path}
|
||||
*/
|
||||
|
||||
Path.fromJSON = function fromJSON(json) {
|
||||
return new Path().fromJSON(json);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inspect the path.
|
||||
* @returns {String}
|
||||
|
||||
@ -19,6 +19,7 @@ var BufferReader = require('../utils/reader');
|
||||
var BufferWriter = require('../utils/writer');
|
||||
var TXDB = require('./txdb');
|
||||
var Path = require('./path');
|
||||
var Address = require('../primitives/address');
|
||||
|
||||
/**
|
||||
* BIP44 Wallet
|
||||
@ -739,7 +740,7 @@ Wallet.prototype.commit = function commit() {
|
||||
*/
|
||||
|
||||
Wallet.prototype.hasAddress = function hasAddress(address) {
|
||||
var hash = bcoin.address.getHash(address, 'hex');
|
||||
var hash = Address.getHash(address, 'hex');
|
||||
if (!hash)
|
||||
return Promise.resolve(false);
|
||||
return this.db.hasAddress(this.wid, hash);
|
||||
@ -752,7 +753,7 @@ Wallet.prototype.hasAddress = function hasAddress(address) {
|
||||
*/
|
||||
|
||||
Wallet.prototype.getPath = co(function* getPath(address) {
|
||||
var hash = bcoin.address.getHash(address, 'hex');
|
||||
var hash = Address.getHash(address, 'hex');
|
||||
var path;
|
||||
|
||||
if (!hash)
|
||||
@ -820,7 +821,7 @@ Wallet.prototype.importKey = co(function* importKey(account, ring, passphrase) {
|
||||
*/
|
||||
|
||||
Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase) {
|
||||
var exists, raw, path;
|
||||
var exists, path;
|
||||
|
||||
if (account && typeof account === 'object') {
|
||||
passphrase = ring;
|
||||
@ -846,16 +847,14 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase)
|
||||
|
||||
yield this.unlock(passphrase);
|
||||
|
||||
raw = ring.toRaw();
|
||||
path = Path.fromAccount(account, ring);
|
||||
path = Path.fromKey(account, ring);
|
||||
|
||||
if (this.master.encrypted) {
|
||||
raw = this.master.encipher(raw, path.hash);
|
||||
assert(raw);
|
||||
path.data = this.master.encipher(path.data, path.hash);
|
||||
assert(path.data);
|
||||
path.encrypted = true;
|
||||
}
|
||||
|
||||
path.imported = raw;
|
||||
ring.path = path;
|
||||
|
||||
this.start();
|
||||
@ -870,6 +869,71 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase)
|
||||
yield this.commit();
|
||||
});
|
||||
|
||||
/**
|
||||
* Import a keyring (will not exist on derivation chain).
|
||||
* Rescanning must be invoked manually.
|
||||
* @param {(String|Number)?} account
|
||||
* @param {KeyRing} ring
|
||||
* @param {(String|Buffer)?} passphrase
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Wallet.prototype.importAddress = co(function* importAddress(account, address) {
|
||||
var unlock = yield this.writeLock.lock();
|
||||
try {
|
||||
return yield this._importAddress(account, address);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Import a keyring (will not exist on derivation chain) without a lock.
|
||||
* @private
|
||||
* @param {(String|Number)?} account
|
||||
* @param {KeyRing} ring
|
||||
* @param {(String|Buffer)?} passphrase
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Wallet.prototype._importAddress = co(function* importAddress(account, address) {
|
||||
var exists, path;
|
||||
|
||||
if (account instanceof Address) {
|
||||
address = account;
|
||||
account = null;
|
||||
}
|
||||
|
||||
if (account == null)
|
||||
account = 0;
|
||||
|
||||
exists = yield this.getPath(address.getHash('hex'));
|
||||
|
||||
if (exists)
|
||||
throw new Error('Address already exists.');
|
||||
|
||||
account = yield this.getAccount(account);
|
||||
|
||||
if (!account)
|
||||
throw new Error('Account not found.');
|
||||
|
||||
if (account.type !== bcoin.account.types.PUBKEYHASH)
|
||||
throw new Error('Cannot import into non-pkh account.');
|
||||
|
||||
path = Path.fromAddress(account, address);
|
||||
|
||||
this.start();
|
||||
|
||||
try {
|
||||
yield account.savePath([path], true);
|
||||
} catch (e) {
|
||||
this.drop();
|
||||
throw e;
|
||||
}
|
||||
|
||||
yield this.commit();
|
||||
});
|
||||
|
||||
/**
|
||||
* Fill a transaction with inputs, estimate
|
||||
* transaction size, calculate fee, and add a change output.
|
||||
@ -1111,7 +1175,7 @@ Wallet.prototype.deriveInputs = co(function* deriveInputs(tx) {
|
||||
*/
|
||||
|
||||
Wallet.prototype.getKeyRing = co(function* getKeyRing(address) {
|
||||
var hash = bcoin.address.getHash(address, 'hex');
|
||||
var hash = Address.getHash(address, 'hex');
|
||||
var path, account;
|
||||
|
||||
if (!hash)
|
||||
|
||||
@ -18,6 +18,7 @@ var constants = bcoin.constants;
|
||||
var BufferReader = require('../utils/reader');
|
||||
var BufferWriter = require('../utils/writer');
|
||||
var Path = require('./path');
|
||||
var Script = require('../script/script');
|
||||
var MAX_POINT = String.fromCharCode(0xdbff, 0xdfff); // U+10FFFF
|
||||
|
||||
/*
|
||||
@ -816,6 +817,8 @@ WalletDB.prototype.saveAddress = co(function* saveAddress(wid, rings) {
|
||||
|
||||
path = path.clone();
|
||||
path.hash = ring.getProgramHash('hex');
|
||||
path.version = -1;
|
||||
path.type = Script.types.SCRIPTHASH;
|
||||
|
||||
yield this.writeAddress(wid, ring.getProgramAddress(), path);
|
||||
}
|
||||
@ -854,6 +857,54 @@ WalletDB.prototype.writeAddress = co(function* writeAddress(wid, address, path)
|
||||
batch.put(layout.p(hash), serializePaths(paths));
|
||||
});
|
||||
|
||||
/**
|
||||
* Save paths to the path map.
|
||||
* @param {WalletID} wid
|
||||
* @param {Path[]} paths
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.savePath = co(function* savePath(wid, paths) {
|
||||
var i, path;
|
||||
|
||||
for (i = 0; i < paths.length; i++) {
|
||||
path = paths[i];
|
||||
yield this.writePath(wid, path);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Save a single address to the path map.
|
||||
* @param {WalletID} wid
|
||||
* @param {Path} path
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.writePath = co(function* writePath(wid, path) {
|
||||
var hash = path.hash;
|
||||
var batch = this.batch(wid);
|
||||
var paths;
|
||||
|
||||
if (this.filter)
|
||||
this.filter.add(hash, 'hex');
|
||||
|
||||
this.emit('save address', path.toAddress(), path);
|
||||
|
||||
paths = yield this.getAddressPaths(hash);
|
||||
|
||||
if (!paths)
|
||||
paths = {};
|
||||
|
||||
if (paths[wid])
|
||||
return;
|
||||
|
||||
paths[wid] = path;
|
||||
|
||||
this.pathCache.set(hash, paths);
|
||||
|
||||
batch.put(layout.p(hash), serializePaths(paths));
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieve paths by hash.
|
||||
* @param {Hash} hash
|
||||
@ -964,7 +1015,9 @@ WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) {
|
||||
return path;
|
||||
}
|
||||
});
|
||||
|
||||
yield this.fillPathNames(paths);
|
||||
|
||||
return paths;
|
||||
});
|
||||
|
||||
|
||||
@ -934,6 +934,25 @@ describe('Wallet', function() {
|
||||
assert(t2.inputs[0].prevout.hash === tx.hash('hex'));
|
||||
}));
|
||||
|
||||
it('should import address', cob(function *() {
|
||||
var key = bcoin.keyring.generate();
|
||||
var w = yield walletdb.create();
|
||||
var options, k, t1, t2, tx;
|
||||
|
||||
yield w.importAddress('default', key.getAddress());
|
||||
|
||||
k = yield w.getPath(key.getHash('hex'));
|
||||
|
||||
assert.equal(k.hash, key.getHash('hex'));
|
||||
}));
|
||||
|
||||
it('should get details', cob(function *() {
|
||||
var w = wallet;
|
||||
var txs = yield w.getRange('foo', { start: 0xdeadbeef - 1000 });
|
||||
var details = yield w.toDetails(txs);
|
||||
assert.equal(details[0].toJSON().outputs[0].path.name, 'foo');
|
||||
}));
|
||||
|
||||
it('should cleanup', cob(function *() {
|
||||
var records = yield walletdb.dump();
|
||||
constants.tx.COINBASE_MATURITY = 100;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user