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

View File

@ -714,6 +714,13 @@ HTTPServer.prototype._init = function _init() {
send(200, address.toJSON()); 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 // Wallet Balance
this.get('/wallet/:id/balance', con(function *(req, res, send, next) { this.get('/wallet/:id/balance', con(function *(req, res, send, next) {
var account = req.options.account; var account = req.options.account;

View File

@ -281,6 +281,14 @@ HTTPWallet.prototype.createAddress = function createAddress(account) {
return this.client.createAddress(this.id, 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 * @see Wallet#setPassphrase
*/ */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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