wallet: use separate branch for nested addrs.

This commit is contained in:
Christopher Jeffrey 2016-10-01 20:05:28 -07:00
parent 19c8959c1a
commit 960393a53f
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
13 changed files with 372 additions and 214 deletions

View File

@ -88,6 +88,12 @@ CLI.prototype.createAddress = co(function* createAddress() {
this.log(addr);
});
CLI.prototype.createNested = co(function* createNested() {
var account = this.argv[0];
var addr = yield this.wallet.createNested(account);
this.log(addr);
});
CLI.prototype.getAccounts = co(function* getAccounts() {
var accounts = yield this.wallet.getAccounts();
this.log(accounts);
@ -343,6 +349,8 @@ CLI.prototype.handleWallet = co(function* handleWallet() {
return yield this.getAccount();
case 'address':
return yield this.createAddress();
case 'nested':
return yield this.createNested();
case 'retoken':
return yield this.retoken();
case 'sign':
@ -370,6 +378,7 @@ CLI.prototype.handleWallet = co(function* handleWallet() {
this.log(' $ account create [account-name]: Create account.');
this.log(' $ account get [account-name]: Get account details.');
this.log(' $ address: Derive new address.');
this.log(' $ nested: Derive new nested address.');
this.log(' $ retoken: Create new api key.');
this.log(' $ send [address] [value]: Send transaction.');
this.log(' $ mktx [address] [value]: Create transaction.');

View File

@ -756,6 +756,27 @@ HTTPClient.prototype.createAddress = function createAddress(id, options) {
return this._post(path, options);
};
/**
* Create address.
* @param {WalletID} id
* @param {Object} options
* @returns {Promise} - Returns Array.
*/
HTTPClient.prototype.createNested = function createNested(id, options) {
var path;
if (!options)
options = {};
if (typeof options === 'string')
options = { account: options };
path = '/wallet/' + id + '/nested';
return this._post(path, options);
};
/*
* Helpers
*/

View File

@ -714,6 +714,13 @@ HTTPServer.prototype._init = function _init() {
send(200, address.toJSON());
}));
// Create nested address
this.post('/wallet/:id/nested', con(function *(req, res, send, next) {
var account = req.options.account;
var address = yield req.wallet.createNested(account);
send(200, address.toJSON());
}));
// Wallet Balance
this.get('/wallet/:id/balance', con(function *(req, res, send, next) {
var account = req.options.account;

View File

@ -281,6 +281,14 @@ HTTPWallet.prototype.createAddress = function createAddress(account) {
return this.client.createAddress(this.id, account);
};
/**
* @see Wallet#createAddress
*/
HTTPWallet.prototype.createNested = function createNested(account) {
return this.client.createNested(this.id, account);
};
/**
* @see Wallet#setPassphrase
*/

View File

@ -37,6 +37,7 @@ function KeyRing(options, network) {
this.network = bcoin.network.get();
this.witness = false;
this.nested = false;
this.publicKey = constants.ZERO_KEY;
this.privateKey = null;
this.script = null;
@ -82,6 +83,11 @@ KeyRing.prototype.fromOptions = function fromOptions(options, network) {
this.witness = options.witness;
}
if (options.nested != null) {
assert(typeof options.nested === 'boolean');
this.nested = options.nested;
}
if (script)
return this.fromScript(key, script, compressed, network);
@ -572,6 +578,8 @@ KeyRing.prototype.compile = function compile(hash, type, version) {
*/
KeyRing.prototype.getHash = function getHash(enc) {
if (this.nested)
return this.getNestedHash(enc);
if (this.script)
return this.getScriptHash(enc);
return this.getKeyHash(enc);
@ -584,6 +592,8 @@ KeyRing.prototype.getHash = function getHash(enc) {
*/
KeyRing.prototype.getAddress = function getAddress(enc) {
if (this.nested)
return this.getNestedAddress(enc);
if (this.script)
return this.getScriptAddress(enc);
return this.getKeyAddress(enc);
@ -704,14 +714,14 @@ KeyRing.prototype.verify = function verify(msg, sig) {
* @returns {ScriptType}
*/
KeyRing.prototype.getType = function getType() {
if (this.program)
return this.program.getType();
KeyRing.prototype.getVersion = function getVersion() {
if (!this.witness)
return -1;
if (this.script)
return this.script.getType();
if (this.nested)
return -1;
return scriptTypes.PUBKEYHASH;
return 0;
};
/**
@ -719,14 +729,19 @@ KeyRing.prototype.getType = function getType() {
* @returns {ScriptType}
*/
KeyRing.prototype.getAddressType = function getAddressType() {
KeyRing.prototype.getType = function getType() {
if (this.nested)
return scriptTypes.SCRIPTHASH;
if (this.witness) {
if (this.script)
return scriptTypes.WITNESSSCRIPTHASH;
return scriptTypes.WITNESSPUBKEYHASH;
}
if (this.script)
return scriptTypes.SCRIPTHASH;
return scriptTypes.PUBKEYHASH;
};
@ -738,6 +753,10 @@ KeyRing.prototype.__defineGetter__('type', function() {
return this.getType();
});
KeyRing.prototype.__defineGetter__('version', function() {
return this.getVersion();
});
KeyRing.prototype.__defineGetter__('scriptHash', function() {
return this.getScriptHash();
});
@ -800,11 +819,12 @@ KeyRing.prototype.toJSON = function toJSON() {
return {
network: this.network.type,
witness: this.witness,
nested: this.nested,
publicKey: this.publicKey.toString('hex'),
script: this.script ? this.script.toRaw().toString('hex') : null,
program: this.program ? this.program.toRaw().toString('hex') : null,
type: constants.scriptTypesByVal[this.type].toLowerCase(),
address: this.getAddress('base58'),
nestedAddress: this.getNestedAddress('base58')
address: this.getAddress('base58')
};
};
@ -818,11 +838,13 @@ KeyRing.prototype.fromJSON = function fromJSON(json) {
assert(json);
assert(typeof json.network === 'string');
assert(typeof json.witness === 'boolean');
assert(typeof json.nested === 'boolean');
assert(typeof json.publicKey === 'string');
assert(!json.script || typeof json.script === 'string');
this.nework = bcoin.network.get(json.network);
this.witness = json.witness;
this.nested = json.nested;
this.publicKey = new Buffer(json.publicKey, 'hex');
if (json.script)
@ -850,6 +872,7 @@ KeyRing.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
p.writeU8(this.witness ? 1 : 0);
p.writeU8(this.nested ? 1 : 0);
if (this.privateKey) {
p.writeVarBytes(this.privateKey);
@ -881,6 +904,7 @@ KeyRing.prototype.fromRaw = function fromRaw(data, network) {
this.network = bcoin.network.get(network);
this.witness = p.readU8() === 1;
this.nested = p.readU8() === 1;
key = p.readVarBytes();

View File

@ -2344,7 +2344,9 @@ utils.isName = function isName(key) {
if (typeof key !== 'string')
return false;
// Maximum worst case size: 80 bytes
if (!/^[\-\._0-9A-Za-z]+$/.test(key))
return false;
return key.length >= 1 && key.length <= 40;
};

View File

@ -16,6 +16,7 @@ var BufferWriter = require('../utils/writer');
var Path = require('./path');
var Script = require('../script/script');
var WalletKey = require('./walletkey');
var HD = require('../hd/hd');
/**
* Represents a BIP44 Account belonging to a {@link Wallet}.
@ -53,6 +54,7 @@ function Account(db, options) {
this.receive = null;
this.change = null;
this.nested = null;
this.wid = 0;
this.id = null;
@ -62,6 +64,7 @@ function Account(db, options) {
this.accountIndex = 0;
this.receiveDepth = 0;
this.changeDepth = 0;
this.nestedDepth = 0;
this.type = Account.types.PUBKEYHASH;
this.m = 1;
this.n = 1;
@ -105,7 +108,7 @@ Account.prototype.fromOptions = function fromOptions(options) {
assert(options, 'Options are required.');
assert(utils.isNumber(options.wid));
assert(utils.isName(options.id), 'Bad Wallet ID.');
assert(bcoin.hd.isHD(options.accountKey), 'Account key is required.');
assert(HD.isHD(options.accountKey), 'Account key is required.');
assert(utils.isNumber(options.accountIndex), 'Account index is required.');
this.wid = options.wid;
@ -138,6 +141,11 @@ Account.prototype.fromOptions = function fromOptions(options) {
this.changeDepth = options.changeDepth;
}
if (options.nestedDepth != null) {
assert(utils.isNumber(options.nestedDepth));
this.nestedDepth = options.nestedDepth;
}
if (options.type != null) {
if (typeof options.type === 'string') {
this.type = Account.types[options.type.toUpperCase()];
@ -218,9 +226,10 @@ Account.prototype.init = co(function* init() {
assert(this.receiveDepth === 0);
assert(this.changeDepth === 0);
assert(this.nestedDepth === 0);
this.initialized = true;
yield this.setDepth(1, 1);
yield this.setDepth(1, 1, 1);
});
/**
@ -235,6 +244,9 @@ Account.prototype.open = function open() {
this.receive = this.deriveReceive(this.receiveDepth - 1);
this.change = this.deriveChange(this.changeDepth - 1);
if (this.witness)
this.nested = this.deriveReceive(this.nestedDepth - 1);
return Promise.resolve(null);
};
@ -249,10 +261,10 @@ Account.prototype.open = function open() {
Account.prototype.pushKey = function pushKey(key) {
var index;
if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key);
if (HD.isExtended(key))
key = HD.fromBase58(key);
if (!bcoin.hd.isPublic(key))
if (!HD.isPublic(key))
throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44())
@ -283,10 +295,10 @@ Account.prototype.pushKey = function pushKey(key) {
*/
Account.prototype.spliceKey = function spliceKey(key) {
if (bcoin.hd.isExtended(key))
key = bcoin.hd.fromBase58(key);
if (HD.isExtended(key))
key = HD.fromBase58(key);
if (!bcoin.hd.isHDPublicKey(key))
if (!HD.isHDPublicKey(key))
throw new Error('Must add HD keys to wallet.');
if (!key.isAccount44())
@ -379,7 +391,7 @@ Account.prototype.removeKey = function removeKey(key) {
*/
Account.prototype.createReceive = function createReceive() {
return this.createKey(false);
return this.createKey(0);
};
/**
@ -388,7 +400,16 @@ Account.prototype.createReceive = function createReceive() {
*/
Account.prototype.createChange = function createChange() {
return this.createKey(true);
return this.createKey(1);
};
/**
* Create a new change address (increments receiveDepth).
* @returns {WalletKey}
*/
Account.prototype.createNested = function createNested() {
return this.createKey(2);
};
/**
@ -397,23 +418,36 @@ Account.prototype.createChange = function createChange() {
* @returns {Promise} - Returns {@link WalletKey}.
*/
Account.prototype.createKey = co(function* createKey(change) {
Account.prototype.createKey = co(function* createKey(branch) {
var ring, lookahead;
if (change) {
ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead);
yield this.saveKey(ring);
yield this.saveKey(lookahead);
this.changeDepth++;
this.change = ring;
} else {
ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
yield this.saveKey(ring);
yield this.saveKey(lookahead);
this.receiveDepth++;
this.receive = ring;
switch (branch) {
case 0:
ring = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
yield this.saveKey(ring);
yield this.saveKey(lookahead);
this.receiveDepth++;
this.receive = ring;
break;
case 1:
ring = this.deriveChange(this.changeDepth);
lookahead = this.deriveChange(this.changeDepth + this.lookahead);
yield this.saveKey(ring);
yield this.saveKey(lookahead);
this.changeDepth++;
this.change = ring;
break;
case 2:
ring = this.deriveNested(this.nestedDepth);
lookahead = this.deriveNested(this.nestedDepth + this.lookahead);
yield this.saveKey(ring);
yield this.saveKey(lookahead);
this.nestedDepth++;
this.nested = ring;
break;
default:
throw new Error('Bad branch: ' + branch);
}
this.save();
@ -428,7 +462,7 @@ Account.prototype.createKey = co(function* createKey(change) {
*/
Account.prototype.deriveReceive = function deriveReceive(index, master) {
return this.deriveKey(false, index, master);
return this.deriveKey(0, index, master);
};
/**
@ -438,7 +472,20 @@ Account.prototype.deriveReceive = function deriveReceive(index, master) {
*/
Account.prototype.deriveChange = function deriveChange(index, master) {
return this.deriveKey(true, index, master);
return this.deriveKey(1, index, master);
};
/**
* Derive a nested address at `index`. Do not increment depth.
* @param {Number} index
* @returns {WalletKey}
*/
Account.prototype.deriveNested = function deriveNested(index, master) {
if (!this.witness)
throw new Error('Cannot derive nested on non-witness account.');
return this.deriveKey(2, index, master);
};
/**
@ -485,7 +532,7 @@ Account.prototype.deriveKey = function deriveKey(branch, index, master) {
var keys = [];
var i, key, shared, ring, hash;
branch = +branch;
assert(typeof branch === 'number');
if (master && master.key) {
key = master.key.deriveAccount44(this.accountIndex);
@ -516,22 +563,6 @@ Account.prototype.deriveKey = function deriveKey(branch, index, master) {
return ring;
};
/**
* Get address type.
* @returns {ScriptType}
*/
Account.prototype.getAddressType = function getAddressType() {
if (this.witness) {
if (this.type === Account.types.MULTISIG)
return Script.types.WITNESSSCRIPTHASH;
return Script.types.WITNESSPUBKEYHASH;
}
if (this.type === Account.types.MULTISIG)
return Script.types.SCRIPTHASH;
return Script.types.PUBKEYHASH;
};
/**
* Save the account to the database. Necessary
* when address depth and keys change.
@ -570,9 +601,9 @@ Account.prototype.savePath = function savePath(path) {
* @returns {Promise} - Returns {@link WalletKey}, {@link WalletKey}.
*/
Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth) {
Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth, nestedDepth) {
var i = -1;
var receive, change, lookahead;
var receive, change, nested, lookahead;
if (receiveDepth > this.receiveDepth) {
for (i = this.receiveDepth; i < receiveDepth; i++) {
@ -604,12 +635,27 @@ Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth) {
this.changeDepth = changeDepth;
}
if (this.witness && nestedDepth > this.nestedDepth) {
for (i = this.nestedDepth; i < nestedDepth; i++) {
nested = this.deriveNested(i);
yield this.saveKey(nested);
}
for (i = nestedDepth; i < nestedDepth + this.lookahead; i++) {
lookahead = this.deriveNested(i);
yield this.saveKey(lookahead);
}
this.nested = nested;
this.nestedDepth = nestedDepth;
}
if (i === -1)
return;
this.save();
return receive;
return receive || nested;
});
/**
@ -629,13 +675,14 @@ Account.prototype.inspect = function inspect() {
address: this.initialized
? this.receive.getAddress()
: null,
nestedAddress: this.initialized
? this.receive.getNestedAddress()
nestedAddress: this.initialized && this.nested
? this.nested.getAddress()
: null,
witness: this.witness,
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
nestedDepth: this.nestedDepth,
accountKey: this.accountKey.xpubkey,
keys: this.keys.map(function(key) {
return key.xpubkey;
@ -662,11 +709,12 @@ Account.prototype.toJSON = function toJSON() {
accountIndex: this.accountIndex,
receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth,
nestedDepth: this.nestedDepth,
receiveAddress: this.receive
? this.receive.getAddress('base58')
: null,
nestedAddress: this.receive
? this.receive.getNestedAddress('base58')
nestedAddress: this.nested
? this.nested.getAddress('base58')
: null,
changeAddress: this.change
? this.change.getAddress('base58')
@ -699,6 +747,7 @@ Account.prototype.fromJSON = function fromJSON(json) {
assert(utils.isNumber(json.accountIndex));
assert(utils.isNumber(json.receiveDepth));
assert(utils.isNumber(json.changeDepth));
assert(utils.isNumber(json.nestedDepth));
assert(Array.isArray(json.keys));
this.wid = json.wid;
@ -711,12 +760,13 @@ Account.prototype.fromJSON = function fromJSON(json) {
this.accountIndex = json.accountIndex;
this.receiveDepth = json.receiveDepth;
this.changeDepth = json.changeDepth;
this.accountKey = bcoin.hd.fromBase58(json.accountKey);
this.nestedDepth = json.nestedDepth;
this.accountKey = HD.fromBase58(json.accountKey);
assert(this.type != null);
for (i = 0; i < json.keys.length; i++) {
key = bcoin.hd.fromBase58(json.keys[i]);
key = HD.fromBase58(json.keys[i]);
this.pushKey(key);
}
@ -733,7 +783,7 @@ Account.prototype.toRaw = function toRaw(writer) {
var i, key;
p.writeU32(this.network.magic);
p.writeVarString(this.name, 'utf8');
p.writeVarString(this.name, 'ascii');
p.writeU8(this.initialized ? 1 : 0);
p.writeU8(this.type);
p.writeU8(this.m);
@ -742,6 +792,7 @@ Account.prototype.toRaw = function toRaw(writer) {
p.writeU32(this.accountIndex);
p.writeU32(this.receiveDepth);
p.writeU32(this.changeDepth);
p.writeU32(this.nestedDepth);
p.writeBytes(this.accountKey.toRaw());
p.writeU8(this.keys.length);
@ -768,7 +819,7 @@ Account.prototype.fromRaw = function fromRaw(data) {
var i, count, key;
this.network = bcoin.network.fromMagic(p.readU32());
this.name = p.readVarString('utf8');
this.name = p.readVarString('ascii');
this.initialized = p.readU8() === 1;
this.type = p.readU8();
this.m = p.readU8();
@ -777,14 +828,15 @@ Account.prototype.fromRaw = function fromRaw(data) {
this.accountIndex = p.readU32();
this.receiveDepth = p.readU32();
this.changeDepth = p.readU32();
this.accountKey = bcoin.hd.fromRaw(p.readBytes(82));
this.nestedDepth = p.readU32();
this.accountKey = HD.fromRaw(p.readBytes(82));
assert(Account.typesByVal[this.type]);
count = p.readU8();
for (i = 0; i < count; i++) {
key = bcoin.hd.fromRaw(p.readBytes(82));
key = HD.fromRaw(p.readBytes(82));
this.pushKey(key);
}

View File

@ -9,10 +9,11 @@
var bcoin = require('../env');
var utils = require('../utils/utils');
var assert = utils.assert;
var constants = bcoin.constants;
var constants = require('../protocol/constants');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var Address = require('../primitives/address');
var Script = require('../script/script');
/**
* Path
@ -43,7 +44,7 @@ function Path() {
this.data = null;
// Currently unused.
this.type = bcoin.script.types.PUBKEYHASH;
this.type = Script.types.PUBKEYHASH;
this.version = -1;
this.hash = null; // Passed in by caller.
}

View File

@ -7,15 +7,19 @@
'use strict';
var bcoin = require('../env');
var utils = require('../utils/utils');
var Locker = require('../utils/locker');
var LRU = require('../utils/lru');
var spawn = require('../utils/spawn');
var co = spawn.co;
var assert = bcoin.utils.assert;
var constants = bcoin.constants;
var DUMMY = new Buffer([0]);
var assert = utils.assert;
var constants = require('../protocol/constants');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var TX = require('../primitives/tx');
var Coin = require('../primitives/coin');
var Outpoint = require('../primitives/outpoint');
var DUMMY = new Buffer([0]);
/*
* Database Layout:
@ -211,8 +215,8 @@ function TXDB(wallet) {
this.options = wallet.db.options;
this.locked = {};
this.locker = new bcoin.locker();
this.coinCache = new bcoin.lru(10000);
this.locker = new Locker();
this.coinCache = new LRU(10000);
this.current = null;
this.balance = null;
@ -446,7 +450,7 @@ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) {
inputs = [];
while (p.left())
inputs.push(bcoin.outpoint.fromRaw(p));
inputs.push(Outpoint.fromRaw(p));
for (i = 0; i < inputs.length; i++) {
input = inputs[i];
@ -558,7 +562,7 @@ TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) {
this.del(layout.o(hash, index));
coin = bcoin.coin.fromTX(tx, index);
coin = Coin.fromTX(tx, index);
// Add input to orphan
for (i = 0; i < orphans.length; i++) {
@ -677,7 +681,7 @@ TXDB.prototype._add = co(function* add(tx, info) {
key = prevout.hash + prevout.index;
// s[outpoint-key] -> [spender-hash]|[spender-input-index]
spender = bcoin.outpoint.fromTX(tx, i).toRaw();
spender = Outpoint.fromTX(tx, i).toRaw();
this.put(layout.s(prevout.hash, prevout.index), spender);
// Add orphan, if no parent transaction is yet known
@ -722,7 +726,7 @@ TXDB.prototype._add = co(function* add(tx, info) {
if (orphans)
continue;
coin = bcoin.coin.fromTX(tx, i);
coin = Coin.fromTX(tx, i);
this.balance.add(coin);
coin = coin.toRaw();
@ -873,7 +877,7 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) {
if (!data)
return;
return bcoin.outpoint.fromRaw(data);
return Outpoint.fromRaw(data);
});
/**
@ -1103,7 +1107,7 @@ TXDB.prototype.__remove = co(function* remove(tx, info) {
if (!path)
continue;
coin = bcoin.coin.fromTX(tx, i);
coin = Coin.fromTX(tx, i);
this.balance.sub(coin);
@ -1321,7 +1325,7 @@ TXDB.prototype.getLocked = function getLocked() {
key = keys[i];
hash = key.slice(0, 64);
index = +key.slice(64);
outpoint = new bcoin.outpoint(hash, index);
outpoint = new Outpoint(hash, index);
outpoints.push(outpoint);
}
@ -1395,10 +1399,10 @@ TXDB.prototype.getOutpoints = function getOutpoints(account) {
parse: function(key) {
if (account != null) {
key = layout.Cc(key);
return new bcoin.outpoint(key[1], key[2]);
return new Outpoint(key[1], key[2]);
}
key = layout.cc(key);
return new bcoin.outpoint(key[0], key[1]);
return new Outpoint(key[0], key[1]);
}
});
};
@ -1563,7 +1567,7 @@ TXDB.prototype.getHistory = function getHistory(account) {
return this.values({
gte: layout.t(constants.NULL_HASH),
lte: layout.t(constants.HIGH_HASH),
parse: bcoin.tx.fromExtended
parse: TX.fromExtended
});
};
@ -1638,7 +1642,7 @@ TXDB.prototype.getCoins = function getCoins(account) {
var parts = layout.cc(key);
var hash = parts[0];
var index = parts[1];
var coin = bcoin.coin.fromRaw(value);
var coin = Coin.fromRaw(value);
coin.hash = hash;
coin.index = index;
key = hash + index;
@ -1691,7 +1695,7 @@ TXDB.prototype.fillHistory = function fillHistory(tx) {
lte: layout.d(hash, 0xffffffff),
parse: function(key, value) {
var index = layout.dd(key)[1];
var coin = bcoin.coin.fromRaw(value);
var coin = Coin.fromRaw(value);
var input = tx.inputs[index];
coin.hash = input.prevout.hash;
coin.index = input.prevout.index;
@ -1740,7 +1744,7 @@ TXDB.prototype.getTX = co(function* getTX(hash) {
if (!tx)
return;
return bcoin.tx.fromExtended(tx);
return TX.fromExtended(tx);
});
/**
@ -1817,7 +1821,7 @@ TXDB.prototype.getCoin = co(function* getCoin(hash, index) {
var coin;
if (data) {
coin = bcoin.coin.fromRaw(data);
coin = Coin.fromRaw(data);
coin.hash = hash;
coin.index = index;
return coin;
@ -1828,7 +1832,7 @@ TXDB.prototype.getCoin = co(function* getCoin(hash, index) {
if (!data)
return;
coin = bcoin.coin.fromRaw(data);
coin = Coin.fromRaw(data);
coin.hash = hash;
coin.index = index;
@ -1851,7 +1855,7 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) {
if (!data)
return;
coin = bcoin.coin.fromRaw(data);
coin = Coin.fromRaw(data);
coin.hash = prevout.hash;
coin.index = prevout.index;
@ -1866,7 +1870,7 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) {
*/
TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, i) {
var prevout = bcoin.outpoint.fromTX(tx, i);
var prevout = Outpoint.fromTX(tx, i);
var spent = yield this.isSpent(prevout.hash, prevout.index);
var coin;
@ -2002,7 +2006,7 @@ TXDB.prototype._zap = co(function* zap(account, age) {
txs = yield this.getRange(account, {
start: 0,
end: bcoin.now() - age
end: utils.now() - age
});
for (i = 0; i < txs.length; i++) {

View File

@ -9,8 +9,9 @@
var bcoin = require('../env');
var EventEmitter = require('events').EventEmitter;
var constants = bcoin.constants;
var constants = require('../protocol/constants');
var utils = require('../utils/utils');
var Locker = require('../utils/locker');
var spawn = require('../utils/spawn');
var co = spawn.co;
var crypto = require('../crypto/crypto');
@ -20,7 +21,12 @@ var BufferWriter = require('../utils/writer');
var TXDB = require('./txdb');
var Path = require('./path');
var Address = require('../primitives/address');
var MTX = require('../primitives/mtx');
var WalletKey = require('./walletkey');
var HD = require('../hd/hd');
var Account = require('./account');
var Input = require('../primitives/input');
var Output = require('../primitives/output');
/**
* BIP44 Wallet
@ -58,8 +64,8 @@ function Wallet(db, options) {
this.db = db;
this.network = db.network;
this.logger = db.logger;
this.writeLock = new bcoin.locker();
this.fundLock = new bcoin.locker();
this.writeLock = new Locker();
this.fundLock = new Locker();
this.wid = 0;
this.id = null;
@ -89,12 +95,12 @@ Wallet.prototype.fromOptions = function fromOptions(options) {
var id, token;
if (!master)
master = bcoin.hd.fromMnemonic(null, this.network);
master = HD.fromMnemonic(null, this.network);
if (!bcoin.hd.isHD(master) && !MasterKey.isMasterKey(master))
master = bcoin.hd.from(master, this.network);
if (!HD.isHD(master) && !MasterKey.isMasterKey(master))
master = HD.from(master, this.network);
if (bcoin.hd.isHD(master))
if (HD.isHD(master))
master = MasterKey.fromKey(master);
assert(MasterKey.isMasterKey(master));
@ -681,7 +687,7 @@ Wallet.prototype.hasAccount = function hasAccount(account) {
*/
Wallet.prototype.createReceive = function createReceive(account) {
return this.createKey(account, false);
return this.createKey(account, 0);
};
/**
@ -691,7 +697,17 @@ Wallet.prototype.createReceive = function createReceive(account) {
*/
Wallet.prototype.createChange = function createChange(account) {
return this.createKey(account, true);
return this.createKey(account, 1);
};
/**
* Create a new nested address (increments receiveDepth).
* @param {(Number|String)?} account
* @returns {Promise} - Returns {@link WalletKey}.
*/
Wallet.prototype.createNested = function createNested(account) {
return this.createKey(account, 2);
};
/**
@ -893,7 +909,7 @@ Wallet.prototype._importKey = co(function* importKey(account, ring, passphrase)
if (!account)
throw new Error('Account not found.');
if (account.type !== bcoin.account.types.PUBKEYHASH)
if (account.type !== Account.types.PUBKEYHASH)
throw new Error('Cannot import into non-pkh account.');
yield this.unlock(passphrase);
@ -949,7 +965,7 @@ Wallet.prototype.importAddress = co(function* importAddress(account, address) {
Wallet.prototype._importAddress = co(function* importAddress(account, address) {
var exists, path;
if (account instanceof Address) {
if (!address) {
address = account;
account = null;
}
@ -957,7 +973,7 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) {
if (account == null)
account = 0;
exists = yield this.getPath(address.getHash('hex'));
exists = yield this.getPath(address);
if (exists)
throw new Error('Address already exists.');
@ -967,7 +983,7 @@ Wallet.prototype._importAddress = co(function* importAddress(account, address) {
if (!account)
throw new Error('Account not found.');
if (account.type !== bcoin.account.types.PUBKEYHASH)
if (account.type !== Account.types.PUBKEYHASH)
throw new Error('Cannot import into non-pkh account.');
path = Path.fromAddress(account, address);
@ -1093,7 +1109,7 @@ Wallet.prototype.createTX = co(function* createTX(options, force) {
throw new Error('No outputs.');
// Create mutable tx
tx = bcoin.mtx();
tx = new MTX();
// Add the outputs
for (i = 0; i < outputs.length; i++)
@ -1255,7 +1271,7 @@ Wallet.prototype.getInputPaths = co(function* getInputPaths(tx) {
var hashes = [];
var i, hash, path;
if (tx instanceof bcoin.input) {
if (tx instanceof Input) {
if (!tx.coin)
throw new Error('Not all coins available.');
@ -1293,7 +1309,7 @@ Wallet.prototype.getOutputPaths = co(function* getOutputPaths(tx) {
var hashes = [];
var i, hash, path;
if (tx instanceof bcoin.output) {
if (tx instanceof Output) {
hash = tx.getHash('hex');
if (hash)
hashes.push(hash);
@ -1337,10 +1353,10 @@ Wallet.prototype.syncOutputDepth = co(function* syncOutputDepth(info) {
*/
Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) {
var receive = [];
var derived = [];
var accounts = {};
var i, j, path, paths, account;
var receiveDepth, changeDepth, ring;
var receive, change, nested, ring;
this.start();
@ -1361,43 +1377,52 @@ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) {
for (i = 0; i < accounts.length; i++) {
paths = accounts[i];
account = paths[0].account;
receiveDepth = -1;
changeDepth = -1;
receive = -1;
change = -1;
nested = -1;
for (j = 0; j < paths.length; j++) {
path = paths[j];
if (path.branch) {
if (path.index > changeDepth)
changeDepth = path.index;
} else {
if (path.index > receiveDepth)
receiveDepth = path.index;
switch (path.branch) {
case 0:
if (path.index > receive)
receive = path.index;
break;
case 1:
if (path.index > change)
change = path.index;
break;
case 2:
if (path.index > nested)
nested = path.index;
break;
}
}
receiveDepth += 2;
changeDepth += 2;
receive += 2;
change += 2;
nested += 2;
account = yield this.getAccount(account);
if (!account)
continue;
ring = yield account.setDepth(receiveDepth, changeDepth);
ring = yield account.setDepth(receive, change, nested);
if (ring)
receive.push(ring);
derived.push(ring);
}
yield this.commit();
if (receive.length > 0) {
this.db.emit('address', this.id, receive);
this.emit('address', receive);
if (derived.length > 0) {
this.db.emit('address', this.id, derived);
this.emit('address', derived);
}
return receive;
return derived;
});
/**
@ -1773,9 +1798,9 @@ Wallet.prototype.getProgram = function getProgram() {
*/
Wallet.prototype.getNestedHash = function getNestedHash(enc) {
if (!this.receive)
if (!this.nested)
return;
return this.receive.getNestedHash(enc);
return this.nested.getHash(enc);
};
/**
@ -1786,9 +1811,9 @@ Wallet.prototype.getNestedHash = function getNestedHash(enc) {
*/
Wallet.prototype.getNestedAddress = function getNestedAddress(enc) {
if (!this.receive)
if (!this.nested)
return;
return this.receive.getNestedAddress(enc);
return this.nested.getAddress(enc);
};
/**
@ -1903,6 +1928,12 @@ Wallet.prototype.__defineGetter__('changeDepth', function() {
return this.account.changeDepth;
});
Wallet.prototype.__defineGetter__('nestedDepth', function() {
if (!this.account)
return -1;
return this.account.nestedDepth;
});
Wallet.prototype.__defineGetter__('accountKey', function() {
if (!this.account)
return;
@ -1921,6 +1952,12 @@ Wallet.prototype.__defineGetter__('change', function() {
return this.account.change;
});
Wallet.prototype.__defineGetter__('nested', function() {
if (!this.account)
return;
return this.account.nested;
});
/**
* Convert the wallet to a more inspection-friendly object.
* @returns {Object}
@ -1997,7 +2034,7 @@ Wallet.prototype.toRaw = function toRaw(writer) {
p.writeU32(this.network.magic);
p.writeU32(this.wid);
p.writeVarString(this.id, 'utf8');
p.writeVarString(this.id, 'ascii');
p.writeU8(this.initialized ? 1 : 0);
p.writeU32(this.accountDepth);
p.writeBytes(this.token);
@ -2020,7 +2057,7 @@ Wallet.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
this.network = bcoin.network.fromMagic(p.readU32());
this.wid = p.readU32();
this.id = p.readVarString('utf8');
this.id = p.readVarString('ascii');
this.initialized = p.readU8() === 1;
this.accountDepth = p.readU32();
this.token = p.readBytes(32);
@ -2083,7 +2120,7 @@ function MasterKey(options) {
this.timer = null;
this.until = 0;
this._destroy = this.destroy.bind(this);
this.locker = new bcoin.locker(this);
this.locker = new Locker(this);
}
/**
@ -2111,7 +2148,7 @@ MasterKey.prototype.fromOptions = function fromOptions(options) {
}
if (options.key) {
assert(bcoin.hd.isHD(options.key));
assert(HD.isHD(options.key));
this.key = options.key;
}
@ -2167,7 +2204,7 @@ MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) {
key = yield crypto.derive(passphrase);
data = crypto.decipher(this.ciphertext, key, this.iv);
this.key = bcoin.hd.fromExtended(data);
this.key = HD.fromExtended(data);
this.start(timeout);
@ -2305,7 +2342,7 @@ MasterKey.prototype._decrypt = co(function* decrypt(passphrase) {
data = yield crypto.decrypt(this.ciphertext, passphrase, this.iv);
this.key = bcoin.hd.fromExtended(data);
this.key = HD.fromExtended(data);
this.encrypted = false;
this.iv = null;
this.ciphertext = null;
@ -2418,7 +2455,7 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) {
return this;
}
this.key = bcoin.hd.fromExtended(p.readVarBytes());
this.key = HD.fromExtended(p.readVarBytes());
return this;
};
@ -2503,7 +2540,7 @@ MasterKey.prototype.fromJSON = function fromJSON(json) {
this.iv = new Buffer(json.iv, 'hex');
this.ciphertext = new Buffer(json.ciphertext, 'hex');
} else {
this.key = bcoin.hd.fromJSON(json.key);
this.key = HD.fromJSON(json.key);
}
return this;

View File

@ -11,15 +11,20 @@ var bcoin = require('../env');
var AsyncObject = require('../utils/async');
var utils = require('../utils/utils');
var spawn = require('../utils/spawn');
var Locker = require('../utils/locker');
var LRU = require('../utils/lru');
var co = spawn.co;
var crypto = require('../crypto/crypto');
var assert = utils.assert;
var constants = bcoin.constants;
var constants = require('../protocol/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
var Wallet = require('./wallet');
var Account = require('./account');
var ldb = require('../db/ldb');
var Bloom = require('../utils/bloom');
/*
* Database Layout:
@ -65,15 +70,15 @@ var layout = {
return key.readUInt32BE(1, true);
},
l: function(id) {
var len = Buffer.byteLength(id, 'utf8');
var len = Buffer.byteLength(id, 'ascii');
var key = new Buffer(1 + len);
key[0] = 0x6c;
if (len > 0)
key.write(id, 1, 'utf8');
key.write(id, 1, 'ascii');
return key;
},
ll: function(key) {
return key.toString('utf8', 1);
return key.toString('ascii', 1);
},
a: function a(wid, index) {
var key = new Buffer(9);
@ -83,16 +88,16 @@ var layout = {
return key;
},
i: function i(wid, name) {
var len = Buffer.byteLength(name, 'utf8');
var len = Buffer.byteLength(name, 'ascii');
var key = new Buffer(5 + len);
key[0] = 0x69;
key.writeUInt32BE(wid, 1, true);
if (len > 0)
key.write(name, 5, 'utf8');
key.write(name, 5, 'ascii');
return key;
},
ii: function ii(key) {
return [key.readUInt32BE(1, true), key.toString('utf8', 5)];
return [key.readUInt32BE(1, true), key.toString('ascii', 5)];
},
R: new Buffer([0x52]),
b: function b(hash) {
@ -147,15 +152,15 @@ function WalletDB(options) {
// We need one read lock for `get` and `create`.
// It will hold locks specific to wallet ids.
this.readLock = new bcoin.locker.mapped();
this.writeLock = new bcoin.locker();
this.txLock = new bcoin.locker();
this.readLock = new Locker.mapped();
this.writeLock = new Locker();
this.txLock = new Locker();
this.widCache = new bcoin.lru(10000);
this.indexCache = new bcoin.lru(10000);
this.accountCache = new bcoin.lru(10000);
this.pathCache = new bcoin.lru(100000);
this.pathMapCache = new bcoin.lru(100000);
this.widCache = new LRU(10000);
this.indexCache = new LRU(10000);
this.accountCache = new LRU(10000);
this.pathCache = new LRU(100000);
this.pathMapCache = new LRU(100000);
// Try to optimize for up to 1m addresses.
// We use a regular bloom filter here
@ -164,10 +169,10 @@ function WalletDB(options) {
// degrades.
// Memory used: 1.7mb
this.filter = this.options.useFilter !== false
? bcoin.bloom.fromRate(1000000, 0.001, -1)
? Bloom.fromRate(1000000, 0.001, -1)
: null;
this.db = bcoin.ldb({
this.db = ldb({
location: this.options.location,
db: this.options.db,
maxOpenFiles: this.options.maxFiles,
@ -489,7 +494,7 @@ WalletDB.prototype._get = co(function* get(wid) {
if (!data)
return;
wallet = bcoin.wallet.fromRaw(this, data);
wallet = Wallet.fromRaw(this, data);
this.register(wallet);
@ -679,7 +684,7 @@ WalletDB.prototype._create = co(function* create(options) {
if (exists)
throw new Error('Wallet already exists.');
wallet = bcoin.wallet.fromOptions(this, options);
wallet = Wallet.fromOptions(this, options);
wallet.wid = this.depth++;
this.register(wallet);
@ -760,7 +765,7 @@ WalletDB.prototype._getAccount = co(function* getAccount(wid, index) {
if (!data)
return;
account = bcoin.account.fromRaw(this, data);
account = Account.fromRaw(this, data);
this.accountCache.set(key, account);
@ -778,8 +783,8 @@ WalletDB.prototype.getAccounts = co(function* getAccounts(wid) {
var i, items, item, name, index, accounts;
items = yield this.db.range({
gte: layout.i(wid, ''),
lte: layout.i(wid, MAX_POINT)
gte: layout.i(wid, '\x00'),
lte: layout.i(wid, '\xff')
});
for (i = 0; i < items.length; i++) {
@ -888,7 +893,7 @@ WalletDB.prototype.createAccount = co(function* createAccount(options) {
if (exists)
throw new Error('Account already exists.');
account = bcoin.account.fromOptions(this, options);
account = Account.fromOptions(this, options);
yield account.init();
@ -959,14 +964,9 @@ WalletDB.prototype.getWalletsByHash = co(function* getWalletsByHash(hash) {
* @returns {Promise}
*/
WalletDB.prototype.saveKey = co(function* saveKey(wid, ring) {
yield this.savePath(wid, ring.toPath());
if (!ring.witness)
return;
yield this.savePath(wid, ring.toNestedPath());
});
WalletDB.prototype.saveKey = function saveKey(wid, ring) {
return this.savePath(wid, ring.toPath());
};
/**
* Save a path to the path map.
@ -1143,8 +1143,8 @@ WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) {
WalletDB.prototype.getWallets = function getWallets() {
return this.db.keys({
gte: layout.l(''),
lte: layout.l(MAX_POINT),
gte: layout.l('\x00'),
lte: layout.l('\xff'),
parse: layout.ll
});
};
@ -1255,7 +1255,7 @@ WalletDB.prototype.resend = co(function* resend() {
if (!data)
continue;
tx = bcoin.tx.fromExtended(data);
tx = TX.fromExtended(data);
this.emit('send', tx);
}
@ -1852,7 +1852,7 @@ Details.prototype._insert = function _insert(vector, target, table) {
io = vector[i];
member = new DetailsMember();
if (io instanceof bcoin.input)
if (io.prevout)
member.value = io.coin ? io.coin.value : 0;
else
member.value = io.value;

View File

@ -129,8 +129,10 @@ WalletKey.prototype.toJSON = function toJSON() {
return {
network: this.network.type,
witness: this.witness,
nested: this.nested,
publicKey: this.publicKey.toString('hex'),
script: this.script ? this.script.toRaw().toString('hex') : null,
program: this.program ? this.program.toRaw().toString('hex') : null,
type: constants.scriptTypesByVal[this.type].toLowerCase(),
wid: this.wid,
id: this.id,
@ -138,8 +140,7 @@ WalletKey.prototype.toJSON = function toJSON() {
account: this.account,
branch: this.branch,
index: this.index,
address: this.getAddress('base58'),
nestedAddress: this.getNestedAddress('base58')
address: this.getAddress('base58')
};
};
@ -178,6 +179,7 @@ WalletKey.prototype.fromHD = function fromHD(account, key, branch, index) {
this.branch = branch;
this.index = index;
this.witness = account.witness;
this.nested = branch === 2;
if (key.privateKey)
return this.fromPrivate(key.privateKey, key.network);
@ -266,7 +268,7 @@ WalletKey.isWalletKey = function isWalletKey(obj) {
* @returns {Boolean}
*/
WalletKey.prototype.toPath = function toPath(nested) {
WalletKey.prototype.toPath = function toPath() {
var path = new Path();
path.id = this.id;
@ -286,30 +288,13 @@ WalletKey.prototype.toPath = function toPath(nested) {
path.keyType = this.keyType;
if (nested) {
assert(this.witness);
path.version = -1;
path.type = Script.types.SCRIPTHASH;
path.hash = this.getNestedHash('hex');
} else {
path.version = this.witness ? 0 : -1;
path.type = this.getAddressType();
path.hash = this.getHash('hex');
}
path.version = this.getVersion();
path.type = this.getType();
path.hash = this.getHash('hex');
return path;
};
/**
* Test whether an object is a WalletKey.
* @param {Object} obj
* @returns {Boolean}
*/
WalletKey.prototype.toNestedPath = function toNestedPath() {
return this.toPath(true);
};
/*
* Expose
*/

View File

@ -507,6 +507,9 @@ describe('Wallet', function() {
var flags = bcoin.constants.flags.STANDARD_VERIFY_FLAGS;
var options, w1, w2, w3, receive, b58, addr, paddr, utx, send, change;
var rec = bullshitNesting ? 'nested' : 'receive';
var depth = bullshitNesting ? 'nestedDepth' : 'receiveDepth';
if (witness)
flags |= bcoin.constants.flags.VERIFY_WITNESS;
@ -531,17 +534,21 @@ describe('Wallet', function() {
yield w3.addKey(w2.accountKey);
// Our p2sh address
b58 = w1.getAddress('base58');
b58 = w1[rec].getAddress('base58');
addr = bcoin.address.fromBase58(b58);
if (witness)
assert.equal(addr.type, scriptTypes.WITNESSSCRIPTHASH);
else
if (witness) {
if (bullshitNesting)
assert.equal(addr.type, scriptTypes.SCRIPTHASH);
else
assert.equal(addr.type, scriptTypes.WITNESSSCRIPTHASH);
} else {
assert.equal(addr.type, scriptTypes.SCRIPTHASH);
}
assert.equal(w1.getAddress('base58'), b58);
assert.equal(w2.getAddress('base58'), b58);
assert.equal(w3.getAddress('base58'), b58);
assert.equal(w1[rec].getAddress('base58'), b58);
assert.equal(w2[rec].getAddress('base58'), b58);
assert.equal(w3[rec].getAddress('base58'), b58);
paddr = w1.getNestedAddress('base58');
assert.equal(w1.getNestedAddress('base58'), paddr);
@ -563,20 +570,21 @@ describe('Wallet', function() {
utx.ts = 1;
utx.height = 1;
assert.equal(w1.receiveDepth, 1);
assert.equal(w1[depth], 1);
yield walletdb.addTX(utx);
yield walletdb.addTX(utx);
yield walletdb.addTX(utx);
assert.equal(w1.receiveDepth, 2);
assert.equal(w1[depth], 2);
assert.equal(w1.changeDepth, 1);
assert(w1.getAddress('base58') !== b58);
b58 = w1.getAddress('base58');
assert.equal(w1.getAddress('base58'), b58);
assert.equal(w2.getAddress('base58'), b58);
assert.equal(w3.getAddress('base58'), b58);
assert(w1[rec].getAddress('base58') !== b58);
b58 = w1[rec].getAddress('base58');
assert.equal(w1[rec].getAddress('base58'), b58);
assert.equal(w2[rec].getAddress('base58'), b58);
assert.equal(w3[rec].getAddress('base58'), b58);
// Create a tx requiring 2 signatures
send = bcoin.mtx();
@ -609,10 +617,10 @@ describe('Wallet', function() {
yield walletdb.addTX(send);
yield walletdb.addTX(send);
assert.equal(w1.receiveDepth, 2);
assert.equal(w1[depth], 2);
assert.equal(w1.changeDepth, 2);
assert(w1.getAddress('base58') === b58);
assert(w1[rec].getAddress('base58') === b58);
assert(w1.change.getAddress('base58') !== change);
change = w1.change.getAddress('base58');
assert.equal(w1.change.getAddress('base58'), change);