more wallet work.

This commit is contained in:
Christopher Jeffrey 2016-05-27 16:20:11 -07:00
parent 3480b8c679
commit 7bb67aa449
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 170 additions and 142 deletions

View File

@ -14,8 +14,6 @@ var assert = utils.assert;
* @exports KeyRing * @exports KeyRing
* @constructor * @constructor
* @param {Object} options * @param {Object} options
* @param {String?} options.label
* @param {Boolean?} options.derived
* @param {HDPrivateKey|HDPublicKey} options.key * @param {HDPrivateKey|HDPublicKey} options.key
* @param {String?} options.path * @param {String?} options.path
* @param {Boolean?} options.change * @param {Boolean?} options.change
@ -40,10 +38,9 @@ function KeyRing(options) {
options = {}; options = {};
this.options = options; this.options = options;
this.label = options.label || '';
this.derived = !!options.derived;
this.addressMap = null; this.addressMap = null;
this.network = bcoin.network.get(options.network);
this.key = options.key; this.key = options.key;
this.path = options.path; this.path = options.path;
this.change = !!options.change; this.change = !!options.change;
@ -63,7 +60,7 @@ function KeyRing(options) {
if (this.m < 1 || this.m > this.n) if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n'); throw new Error('m ranges between 1 and n');
this.addKey(this.getPublicKey()); this.addKey(this.key);
if (options.keys) { if (options.keys) {
for (i = 0; i < options.keys.length; i++) for (i = 0; i < options.keys.length; i++)
@ -129,7 +126,13 @@ KeyRing.prototype.removeKey = function removeKey(key) {
*/ */
KeyRing.prototype.getPublicKey = function getPublicKey(enc) { KeyRing.prototype.getPublicKey = function getPublicKey(enc) {
return this.key.getPublicKey(enc); if (enc === 'base58')
return utils.toBase58(this.key);
if (enc === 'hex')
return this.key.toString('hex');
return this.key;
}; };
/** /**
@ -455,6 +458,7 @@ KeyRing.prototype.scriptInputs = function scriptInputs(tx, index) {
* Build input scripts and sign inputs for a transaction. Only attempts * Build input scripts and sign inputs for a transaction. Only attempts
* to build/sign inputs that are redeemable by this address. * to build/sign inputs that are redeemable by this address.
* @param {MTX} tx * @param {MTX} tx
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key.
* @param {Number?} index - Index of input. If not present, * @param {Number?} index - Index of input. If not present,
* it will attempt to build and sign all redeemable inputs. * it will attempt to build and sign all redeemable inputs.
* @param {SighashType?} type * @param {SighashType?} type
@ -539,6 +543,54 @@ KeyRing.prototype.__defineGetter__('address', function() {
return this.getAddress(); return this.getAddress();
}); });
/**
* Convert an KeyRing to a more json-friendly object.
* @param {String?} passphrase - KeyRing passphrase
* @returns {Object}
*/
KeyRing.prototype.toJSON = function toJSON() {
return {
v: 1,
name: 'address',
address: this.getAddress(),
network: this.network.type,
change: this.change,
index: this.index,
path: this.path,
key: utils.toBase58(this.key),
type: this.type,
witness: this.witness,
keys: this.keys.map(utils.toBase58),
m: this.m,
n: this.n
};
};
/**
* Instantiate an KeyRing from a jsonified transaction object.
* @param {Object} json - The jsonified transaction object.
* @param {String?} passphrase - KeyRing passphrase
* @returns {KeyRing}
*/
KeyRing.fromJSON = function fromJSON(json) {
assert.equal(json.v, 1);
assert.equal(json.name, 'address');
return new KeyRing({
nework: json.network,
change: json.change,
index: json.index,
path: json.path,
key: utils.fromBase58(json.key),
type: json.type,
witness: json.witness,
keys: json.keys.map(utils.fromBase58),
m: json.m,
n: json.n
});
};
/* /*
* Expose * Expose
*/ */

View File

@ -353,8 +353,9 @@ MTX.prototype.createSignature = function createSignature(index, prev, key, type,
/** /**
* Sign an input. * Sign an input.
* @param {Number} index - Index of input being signed. * @param {Number} index - Index of input being signed.
* @param {Address} addr - Address used to sign. The address * @param {KeyRing} addr - Address used to sign. The address
* must be able to redeem the coin. * must be able to redeem the coin.
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key.
* @param {SighashType} type * @param {SighashType} type
* @returns {Boolean} Whether the input was able to be signed. * @returns {Boolean} Whether the input was able to be signed.
* @throws on unavailable coins. * @throws on unavailable coins.
@ -623,8 +624,9 @@ MTX.prototype.isSigned = function isSigned() {
/** /**
* Built input scripts (or witnesses) and sign the inputs. * Built input scripts (or witnesses) and sign the inputs.
* @param {Number} index - Index of input being signed. * @param {Number} index - Index of input being signed.
* @param {Address} addr - Address used to sign. The address * @param {KeyRing} addr - Address used to sign. The address
* must be able to redeem the coin. * must be able to redeem the coin.
* @param {HDPrivateKey|KeyPair|Buffer} key - Private key.
* @param {SighashType} type * @param {SighashType} type
* @returns {Boolean} Whether the input was able to be signed. * @returns {Boolean} Whether the input was able to be signed.
* @throws on unavailable coins. * @throws on unavailable coins.
@ -656,7 +658,7 @@ MTX.prototype.sign = function sign(index, addr, key, type) {
* tx.addOutput({ address: ..., value: new bn(100000) }); * tx.addOutput({ address: ..., value: new bn(100000) });
* tx.addOutput({ address: ..., value: utils.satoshi('0.1') }); * tx.addOutput({ address: ..., value: utils.satoshi('0.1') });
* tx.addOutput(receivingWallet, utils.satoshi('0.1')); * tx.addOutput(receivingWallet, utils.satoshi('0.1'));
* @param {Wallet|Address|Object} obj - Wallet, Address, * @param {Wallet|KeyRing|Object} obj - Wallet, Address,
* or options (see {@link Script.createOutputScript} for options). * or options (see {@link Script.createOutputScript} for options).
* @param {Amount?} value - Only needs to be present for non-options. * @param {Amount?} value - Only needs to be present for non-options.
*/ */

View File

@ -83,6 +83,7 @@ function Wallet(options) {
this.copayBIP45 = options.copayBIP45 || false; this.copayBIP45 = options.copayBIP45 || false;
this.lookahead = options.lookahead != null ? options.lookahead : 5; this.lookahead = options.lookahead != null ? options.lookahead : 5;
this.cosignerIndex = -1; this.cosignerIndex = -1;
this.initialized = false;
this.type = options.type || 'pubkeyhash'; this.type = options.type || 'pubkeyhash';
this.derivation = options.derivation || 'bip44'; this.derivation = options.derivation || 'bip44';
@ -154,8 +155,9 @@ Wallet.prototype._init = function _init() {
var self = this; var self = this;
var i; var i;
assert(!this._initialized); assert(!this.initialized);
this._initialized = true;
this.initialized = true;
if (Object.keys(this.addressMap).length === 0) { if (Object.keys(this.addressMap).length === 0) {
for (i = 0; i < this.receiveDepth - 1; i++) for (i = 0; i < this.receiveDepth - 1; i++)
@ -178,15 +180,32 @@ Wallet.prototype._init = function _init() {
assert(!this.receiveAddress.change); assert(!this.receiveAddress.change);
assert(this.changeAddress.change); assert(this.changeAddress.change);
if (!this.provider)
return;
this.provider.setID(this.id);
this.on('error', function(err) { this.on('error', function(err) {
bcoin.debug('Wallet Error: %s', err.message); bcoin.debug('Wallet Error: %s', err.message);
}); });
this.setProvider(this.provider, function(err) {
if (err)
return self.emit('error', err);
self.loaded = true;
self.emit('open');
});
};
Wallet.prototype.setProvider = function setProvider(provider, callback) {
var self = this;
if (!provider)
return callback();
if (this.provider !== provider)
this.provider.destroy();
this.provider = provider;
this.provider.setID(this.id);
this.provider.on('error', function(err) { this.provider.on('error', function(err) {
self.emit('error', err); self.emit('error', err);
}); });
@ -212,13 +231,7 @@ Wallet.prototype._init = function _init() {
self.emit('unconfirmed', tx); self.emit('unconfirmed', tx);
}); });
this.provider.open(function(err) { this.provider.open(callback);
if (err)
return self.emit('error', err);
self.loaded = true;
self.emit('open');
});
}; };
/** /**
@ -392,9 +405,6 @@ Wallet.prototype.getID = function getID() {
var publicKey = this.accountKey.publicKey; var publicKey = this.accountKey.publicKey;
var p; var p;
if (this.options.id)
return this.options.id;
p = new BufferWriter(); p = new BufferWriter();
p.writeU8(0x03); p.writeU8(0x03);
p.writeU8(0xbe); p.writeU8(0xbe);
@ -484,9 +494,9 @@ Wallet.prototype.deriveChange = function deriveChange(index) {
*/ */
Wallet.prototype.deriveAddress = function deriveAddress(change, index) { Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
var path, data, key, options, address; var i, path, data, key, options, address;
assert(this._initialized); assert(this.initialized);
if (typeof change === 'string') if (typeof change === 'string')
path = change; path = change;
@ -517,7 +527,7 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
options = { options = {
network: this.network, network: this.network,
key: key, key: key.publicKey,
change: data.change, change: data.change,
index: data.index, index: data.index,
path: data.path, path: data.path,
@ -525,15 +535,15 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
witness: this.witness, witness: this.witness,
m: this.m, m: this.m,
n: this.n, n: this.n,
keys: [], keys: []
derived: true
}; };
this.keys.forEach(function(key, cosignerIndex) { for (i = 0; i < this.keys.length; i++) {
var path = this.createPath(cosignerIndex, data.change, data.index); key = this.keys[i];
path = this.createPath(i, data.change, data.index);
key = key.derive(path); key = key.derive(path);
options.keys.push(key.publicKey); options.keys.push(key.publicKey);
}, this); }
address = new bcoin.keyring(options); address = new bcoin.keyring(options);
@ -724,7 +734,7 @@ Wallet.prototype.fill = function fill(tx, options, callback) {
if (!options) if (!options)
options = {}; options = {};
assert(this._initialized); assert(this.initialized);
this.getCoins(function(err, coins) { this.getCoins(function(err, coins) {
if (err) if (err)
@ -858,6 +868,9 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) {
if (!tx.checkInputs(height)) if (!tx.checkInputs(height))
return callback(new Error('CheckInputs failed.')); return callback(new Error('CheckInputs failed.'));
if (!self.scriptInputs(tx))
return callback(new Error('scriptInputs failed.'));
return callback(null, tx); return callback(null, tx);
}); });
}; };
@ -1095,7 +1108,7 @@ Wallet.prototype._scan = function _scan(getByAddress, callback) {
var depth = { changeDepth: 0, receiveDepth: 0 }; var depth = { changeDepth: 0, receiveDepth: 0 };
var all = []; var all = [];
assert(this._initialized); assert(this.initialized);
(function chainCheck(change) { (function chainCheck(change) {
var addressIndex = 0; var addressIndex = 0;
@ -1501,13 +1514,13 @@ Wallet.prototype.inspect = function inspect() {
network: this.network.type, network: this.network.type,
m: this.m, m: this.m,
n: this.n, n: this.n,
keyAddress: this._initialized keyAddress: this.initialized
? this.keyAddress ? this.keyAddress
: null, : null,
scriptAddress: this._initialized scriptAddress: this.initialized
? this.scriptAddress ? this.scriptAddress
: null, : null,
programAddress: this._initialized programAddress: this.initialized
? this.programAddress ? this.programAddress
: null, : null,
witness: this.witness, witness: this.witness,
@ -1653,7 +1666,6 @@ MasterKey.fromJSON = function fromJSON(json) {
}); });
}; };
/* /*
* Expose * Expose
*/ */

View File

@ -40,6 +40,7 @@ function WalletDB(options) {
EventEmitter.call(this); EventEmitter.call(this);
this.providers = [];
this.options = options; this.options = options;
this.loaded = false; this.loaded = false;
this.network = bcoin.network.get(options.network); this.network = bcoin.network.get(options.network);
@ -136,14 +137,14 @@ WalletDB.prototype._init = function _init() {
this.tx.on('tx', function(tx, map) { this.tx.on('tx', function(tx, map) {
self.emit('tx', tx, map); self.emit('tx', tx, map);
map.all.forEach(function(id) { map.all.forEach(function(id) {
self.emit(id + ' tx', tx); self.fire(id, 'tx', tx);
}); });
}); });
this.tx.on('confirmed', function(tx, map) { this.tx.on('confirmed', function(tx, map) {
self.emit('confirmed', tx, map); self.emit('confirmed', tx, map);
map.all.forEach(function(id) { map.all.forEach(function(id) {
self.emit(id + ' confirmed', tx); self.fire(id, 'confirmed', tx);
}); });
utils.forEachSerial(map.output, function(id, next) { utils.forEachSerial(map.output, function(id, next) {
self.syncOutputDepth(id, tx, next); self.syncOutputDepth(id, tx, next);
@ -156,7 +157,7 @@ WalletDB.prototype._init = function _init() {
this.tx.on('unconfirmed', function(tx, map) { this.tx.on('unconfirmed', function(tx, map) {
self.emit('unconfirmed', tx, map); self.emit('unconfirmed', tx, map);
map.all.forEach(function(id) { map.all.forEach(function(id) {
self.emit(id + ' unconfirmed', tx); self.fire(id, 'unconfirmed', tx);
}); });
}); });
@ -165,12 +166,12 @@ WalletDB.prototype._init = function _init() {
self.emit('updated', tx, map); self.emit('updated', tx, map);
map.all.forEach(function(id) { map.all.forEach(function(id) {
self.emit(id + ' updated', tx); self.fire(id, 'updated', tx);
}); });
utils.forEachSerial(map.output, function(id, next) { utils.forEachSerial(map.output, function(id, next) {
if (self.listeners('balance').length === 0 if (self.listeners('balance').length === 0
&& self.listeners(id + ' balance').length === 0) { && !self.hasListener(id, ' balance')) {
return next(); return next();
} }
@ -181,7 +182,7 @@ WalletDB.prototype._init = function _init() {
balances[id] = balance; balances[id] = balance;
self.emit('balance', balance, id); self.emit('balance', balance, id);
self.emit(id + ' balance', balance); self.fire(id, 'balance', balance);
next(); next();
}); });
@ -447,28 +448,38 @@ WalletDB.prototype.get = function get(id, callback) {
/** /**
* Save a wallet to the database (setup ida and encrypt). * Save a wallet to the database (setup ida and encrypt).
* @param {WalletID?} id * @param {Wallet} wallet
* @param {Wallet} options
* @param {Function} callback * @param {Function} callback
*/ */
WalletDB.prototype.save = function save(wallet, callback) { WalletDB.prototype.save = function save(wallet, callback) {
var self = this;
if (Array.isArray(wallet)) {
return utils.forEachSerial(wallet, function(wallet, next) {
self.save(wallet, next);
}, callback);
}
this.saveJSON(wallet.id, wallet.toJSON(), callback); this.saveJSON(wallet.id, wallet.toJSON(), callback);
}; };
/** /**
* Remove wallet from the database. Destroy wallet if passed in. * Remove wallet from the database. Destroy wallet if passed in.
* @param {WalletID|Wallet} id * @param {WalletID} id
* @param {Function} callback * @param {Function} callback
*/ */
WalletDB.prototype.remove = function remove(id, callback) { WalletDB.prototype.remove = function remove(id, callback) {
var self = this;
if (Array.isArray(wallet)) {
return utils.forEachSerial(id, function(id, next) {
self.remove(id, next);
}, callback);
}
return this.removeJSON(id, callback); return this.removeJSON(id, callback);
}; };
/** /**
* Create a new wallet, save to database, setup provider. * Create a new wallet, save to database, setup provider.
* @param {WalletID?} id
* @param {Object} options - See {@link Wallet}. * @param {Object} options - See {@link Wallet}.
* @param {Function} callback - Returns [Error, {@link Wallet}]. * @param {Function} callback - Returns [Error, {@link Wallet}].
*/ */
@ -703,38 +714,53 @@ WalletDB.prototype.provider = function provider() {
}; };
WalletDB.prototype.register = function register(id, provider) { WalletDB.prototype.register = function register(id, provider) {
if (!this.listeners[id]) if (!this.providers[id])
this.listeners[id] = []; this.providers[id] = [];
if (this.listeners[id].indexOf(provider) !== -1) if (this.providers[id].indexOf(provider) === -1)
this.listeners[id].push(provider); this.providers[id].push(provider);
}; };
WalletDB.prototype.unregister = function unregister(id, provider) { WalletDB.prototype.unregister = function unregister(id, provider) {
var listeners = this.listeners[id]; var providers = this.providers[id];
var i; var i;
if (!listeners) if (!providers)
return; return;
i = listeners.indexOf(provider); i = providers.indexOf(provider);
if (i !== -1) if (i !== -1)
listeners.splice(i, 1); providers.splice(i, 1);
if (listeners.length === 0) if (providers.length === 0)
delete this.listeners[id]; delete this.providers[id];
}; };
WalletDB.prototype.fire = function fire(id) { WalletDB.prototype.fire = function fire(id) {
var args = Array.prototype.slice.call(arguments, 1); var args = Array.prototype.slice.call(arguments, 1);
var listeners = this.listeners[id]; var providers = this.providers[id];
var i; var i;
if (!listeners) if (!providers)
return; return;
for (i = 0; i < listeners.length; i++) for (i = 0; i < providers.length; i++)
listeners.emit.apply(listener, args); providers[i].emit.apply(providers[i], args);
};
WalletDB.prototype.hasListener = function hasListener(id, event) {
var providers = this.providers[id];
var i;
if (!providers)
return false;
for (i = 0; i < providers.length; i++) {
if (providers[i].listeners(event).length !== 0)
return true;
}
return false;
}; };
/** /**
@ -800,26 +826,7 @@ Provider.prototype.setID = function setID(id) {
assert(!this.id, 'ID has already been set.'); assert(!this.id, 'ID has already been set.');
this.id = id; this.id = id;
this.db.register(this.id, this);
this.db.on(id + ' tx', this._onTX = function(tx) {
self.emit('tx', tx);
});
this.db.on(id + ' updated', this._onUpdated = function(tx) {
self.emit('updated', tx);
});
this.db.on(id + ' confirmed', this._onConfirmed = function(tx) {
self.emit('confirmed', tx);
});
this.db.on(id + ' unconfirmed', this._onUnconfirmed = function(tx) {
self.emit('unconfirmed', tx);
});
this.db.on(id + ' balance', this._onBalance = function(balance) {
self.emit('balance', balance);
});
}; };
/** /**
@ -832,35 +839,12 @@ Provider.prototype.close =
Provider.prototype.destroy = function destroy(callback) { Provider.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback); callback = utils.ensure(callback);
if (!this.db) if (!this.id)
return utils.nextTick(callback); return utils.nextTick(callback);
if (this._onTX) { this.db.unregister(this.id, this);
this.db.removeListener(this.id + ' tx', this._onTX);
delete this._onTX;
}
if (this._onUpdated) {
this.db.removeListener(this.id + ' updated', this._onUpdated);
delete this._onUpdated;
}
if (this._onConfirmed) {
this.db.removeListener(this.id + ' confirmed', this._onConfirmed);
delete this._onConfirmed;
}
if (this._onUnconfirmed) {
this.db.removeListener(this.id + ' unconfirmed', this._onUnconfirmed);
delete this._onUnconfirmed;
}
if (this._onBalance) {
this.db.removeListener(this.id + ' balance', this._onBalance);
delete this._onBalance;
}
this.db = null; this.db = null;
this.id = null;
return utils.nextTick(callback); return utils.nextTick(callback);
}; };
@ -999,11 +983,9 @@ Provider.prototype.save = function save(wallet, callback) {
}; };
/** /**
* Notify the provider backend that a new address was * Add a key to the wallet.
* derived (not technically necessary if you're * @param {HDPublicKey} key
* implementing a provider). * @param {Function} callback
* @param {Wallet} wallet
* @param {Address} address
*/ */
Provider.prototype.addKey = function addKey(key, callback) { Provider.prototype.addKey = function addKey(key, callback) {
@ -1011,23 +993,9 @@ Provider.prototype.addKey = function addKey(key, callback) {
}; };
/** /**
* Notify the provider backend that a new address was * Remove a key from the wallet.
* derived (not technically necessary if you're * @param {HDPublicKey} key
* implementing a provider). * @param {Function} callback
* @param {Wallet} wallet
* @param {Address} address
*/
// Provider.prototype.deriveInputs = function deriveInputs(tx, index, callback) {
// return this.db.deriveInputs(this.id, tx, index, callback);
// };
/**
* Notify the provider backend that a new address was
* derived (not technically necessary if you're
* implementing a provider).
* @param {Wallet} wallet
* @param {Address} address
*/ */
Provider.prototype.removeKey = function removeKey(key, callback) { Provider.prototype.removeKey = function removeKey(key, callback) {
@ -1035,11 +1003,8 @@ Provider.prototype.removeKey = function removeKey(key, callback) {
}; };
/** /**
* Notify the provider backend that a new address was * Create a receiving address.
* derived (not technically necessary if you're * @param {Function} callback
* implementing a provider).
* @param {Wallet} wallet
* @param {Address} address
*/ */
Provider.prototype.createReceive = function createReceive(callback) { Provider.prototype.createReceive = function createReceive(callback) {
@ -1047,11 +1012,8 @@ Provider.prototype.createReceive = function createReceive(callback) {
}; };
/** /**
* Notify the provider backend that a new address was * Create a change address.
* derived (not technically necessary if you're * @param {Function} callback
* implementing a provider).
* @param {Wallet} wallet
* @param {Address} address
*/ */
Provider.prototype.createChange = function createChange(callback) { Provider.prototype.createChange = function createChange(callback) {