createTX. hd fixes.

This commit is contained in:
Christopher Jeffrey 2016-02-02 02:47:21 -08:00
parent 42e17bc0f2
commit 156601e40a
8 changed files with 443 additions and 202 deletions

View File

@ -580,6 +580,26 @@ pool.on('fork', function(tip1, tip2) {
}); });
``` ```
### Transaction Building
Transaction building happens in 4 stages:
1. __Outputs__: Adding of the outputs (where we want to send the coins).
2. __Filling__: Filling of the unspent outputs as the inputs, calculation of
fee, and potential addition of a change address.
3. __Scripting__: Compilation of the of the input scripts (minus the
signatures). This will fill `n` empty signature slots (`OP_0`).
- __Potential recalculation of the fee__: Now that the redeem script is
available to the primitive TX object, it can likely calculate a lower fee.
Add value to the change address if the fee is lower than we thought.
4. __Signing__: Signing of the inputs. If this is a multisig transaction, we
have to wait for other signers to sign it before it is finalized.
- __Finalization__: Once the final signer signs the transaction, empty
signature slots will be removed as they are no longer necessary.
Once these steps are completed, the transaction should verify and is able to be
broadcast to the pool.
## API Documentation ## API Documentation
### Objects ### Objects

View File

@ -109,6 +109,7 @@ Address.prototype.setRedeem = function setRedeem(redeem) {
Address.prototype.addKey = function addKey(key) { Address.prototype.addKey = function addKey(key) {
var old = this.getScriptAddress(); var old = this.getScriptAddress();
var cur;
key = utils.toBuffer(key); key = utils.toBuffer(key);
@ -122,11 +123,16 @@ Address.prototype.addKey = function addKey(key) {
this.keys.push(key); this.keys.push(key);
this.keys = utils.sortKeys(this.keys); this.keys = utils.sortKeys(this.keys);
this.emit('scriptaddress', old, this.getScriptAddress());
cur = this.getScriptAddress();
if (old !== cur)
this.emit('scriptaddress', old, cur);
}; };
Address.prototype.removeKey = function removeKey(key) { Address.prototype.removeKey = function removeKey(key) {
var old = this.getScriptAddress(); var old = this.getScriptAddress();
var cur;
key = utils.toBuffer(key); key = utils.toBuffer(key);
@ -142,7 +148,11 @@ Address.prototype.removeKey = function removeKey(key) {
this.keys.splice(index, 1); this.keys.splice(index, 1);
this.keys = utils.sortKeys(this.keys); this.keys = utils.sortKeys(this.keys);
this.emit('scriptaddress', old, this.getScriptAddress());
cur = this.getScriptAddress();
if (old !== cur)
this.emit('scriptaddress', old, this.getScriptAddress());
}; };
Address.prototype.getPrivateKey = function getPrivateKey(enc) { Address.prototype.getPrivateKey = function getPrivateKey(enc) {

View File

@ -168,153 +168,178 @@ function HDPrivateKey(options) {
this.master = options.master; this.master = options.master;
} }
this.purpose = options.purpose != null
? options.purpose
: 44;
this.coinType = options.coinType != null
? options.coinType
: (network.type === 'main' ? 0 : 1);
this.accountIndex = options.accountIndex != null
? options.accountIndex
: 0;
this.isChange = options.isChange != null
? options.isChange
: false;
// this.addressIndex = options.addressIndex != null
// ? options.addressIndex
// : 0;
this.isPrivate = true; this.isPrivate = true;
} }
HDPrivateKey.prototype.scan = function scan(txs) { HDPrivateKey.prototype.scan = function scan(options, txByAddress, callback) {
var keys = []; var keys = [];
var purpose, coinType, root;
assert(this.isMaster); if (!callback) {
callback = txByAddress;
txByAddress = options;
options = null;
}
if (!options)
options = {};
purpose = options.purpose;
coinType = options.coinType;
if (purpose == null)
purpose = 44;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
assert(utils.isFinite(purpose));
assert(utils.isFinite(coinType));
// 0. get the root node // 0. get the root node
var root = master root = this
.derive(44, true) .derive(purpose, true)
.derive(network.type === 'main' ? 0 : 1, true); .derive(coinType, true);
return (function scanner(accountIndex) { return (function scanner(accountIndex) {
var account, chain, addressIndex, address, addr, i; var addressIndex = 0;
var total = 0;
var gap = 0; var gap = 0;
// 1. derive the first account's node (index = 0) // 1. derive the first account's node (index = 0)
account = root.derive(accountIndex, true); var account = root.derive(accountIndex, true);
// 2. derive the external chain node of this account // 2. derive the external chain node of this account
chain = account.derive(0); var chain = account.derive(0);
// 3. scan addresses of the external chain; // 3. scan addresses of the external chain;
// respect the gap limit described below // respect the gap limit described below
addressIndex = 0; return (function next() {
main: var address = chain.derive(addressIndex++);
for (;;) { var addr = bcoin.address.hash2addr(
address = chain.derive(addressIndex++);
addr = bcoin.address.hash2addr(
bcoin.address.key2hash(address.publicKey), bcoin.address.key2hash(address.publicKey),
'pubkey'); 'pubkey');
for (i = 0; i < txs.length; i++) { return txByAddress(addr, function(err, txs) {
txs[i].fillPrevout(txs); var result;
if (txs[i].testInputs(addr) || txs[i].testOutputs(addr)) {
keys.push(address);
gap = 0;
} else {
// 4. if no transactions are found on the
// external chain, stop discovery
if (++gap >= 20)
return keys;
}
}
}
// 5. if there are some transactions, increase if (err)
// the account index and go to step 1 return callback(err);
return scanner(accountIndex + 1);
if (txs) {
if (typeof txs === 'boolean')
result = txs;
else if (Array.isArray(txs))
result = txs.length > 0;
else
result = false;
}
if (result) {
keys.push(address);
total++;
gap = 0;
return next();
}
if (++gap < 20)
return next();
// 4. if no transactions are found on the
// external chain, stop discovery
if (total === 0)
return callback(null, keys);
// 5. if there are some transactions, increase
// the account index and go to step 1
return scanner(accountIndex + 1);
});
})();
})(0); })(0);
}; };
HDPrivateKey.prototype.__defineGetter__('addressIndex', function() { HDPrivateKey.prototype.deriveBIP44 = function deriveBIP44(options) {
var index = this.childIndex; var purpose = options.purpose;
assert(index < contants.hd.hardened); var coinType = options.coinType;
return index; var accountIndex = options.accountIndex;
}); var chain = options.chain;
var addressIndex = options.addressIndex;
HDPrivateKey.prototype._deriveBIP44 = function _deriveBIP44() {
var child; var child;
assert(this.isMaster); if (purpose == null)
purpose = 44;
if (options.purpose == null) if (coinType == null)
options.purpose = 44; coinType = network.type === 'main' ? 0 : 1;
if (options.coinType == null) if (chain == null)
options.coinType = network.type === 'main' ? 0 : 1; chain = options.change ? 1 : 0;
assert(utils.isFinite(purpose));
assert(utils.isFinite(coinType));
assert(utils.isFinite(accountIndex));
assert(utils.isFinite(chain));
assert(utils.isFinite(addressIndex));
child = this child = this
.derive(options.purpose, true) .derive(purpose, true)
.derive(options.coinType, true) .derive(coinType, true)
.derive(options.accountIndex, true) .derive(accountIndex, true)
.derive(options.isChange ? 1 : 0) .derive(chain)
.derive(options.addressIndex); .derive(addressIndex);
child.purpose = options.purpose;
child.coinType = options.coinType;
child.accountIndex = options.accountIndex;
child.isChange = options.isChange;
child.addressIndex = options.addressIndex;
return child; return child;
}; };
HDPrivateKey.prototype.deriveAccount = function deriveAccount(accountIndex) { HDPrivateKey.prototype.deriveAccount = function deriveAccount(accountIndex) {
return this._deriveBIP44({ return this.deriveBIP44({
purpose: this.purpose, accountIndex: accountIndex,
coinType: this.coinType, chain: 0,
accountIndex: accountIndex != null ? accountIndex : this.accountIndex + 1,
isChange: false,
addressIndex: 0 addressIndex: 0
}); });
}; };
HDPrivateKey.prototype.deriveChange = function deriveChange(addressIndex, accountIndex) { HDPrivateKey.prototype.deriveChange = function deriveChange(accountIndex, addressIndex) {
return this._deriveBIP44({ return this.deriveBIP44({
purpose: this.purpose, accountIndex: accountIndex,
coinType: this.coinType, chain: 1,
accountIndex: accountIndex != null ? accountIndex : this.accountIndex, addressIndex: addressIndex
isChange: true,
addressIndex: addressIndex != null ? addressIndex : this.addressIndex + 1
}); });
}; };
HDPrivateKey.prototype.deriveAddress = function deriveAddress(addressIndex, accountIndex) { HDPrivateKey.prototype.deriveAddress = function deriveAddress(accountIndex, addressIndex) {
return this._deriveBIP44({ return this.deriveBIP44({
purpose: this.purpose, accountIndex: accountIndex,
coinType: this.coinType, chain: 0,
accountIndex: accountIndex != null ? accountIndex : this.accountIndex, addressIndex: addressIndex
isChange: false,
addressIndex: addressIndex != null ? addressIndex : this.addressIndex + 1
}); });
}; };
HDPrivateKey.prototype.toPath = function toPath(data) { HDPrivateKey.getPath = function getPath(options) {
assert(!this.isMaster); var purpose, coinType, accountIndex, chain, addressIndex;
return HDPrivateKey.getPath(
this.accountIndex, this.addressIndex, this.isChange);
};
HDPrivateKey.getPath = function toPath(accountIndex, addressIndex, isChange) { if (!options)
return 'm/44\'/' options = {};
+ (network.type === 'main' ? '0' : '1') + '\'' + '/'
purpose = options.purpose;
coinType = options.coinType;
accountIndex = options.accountIndex;
chain = options.chain;
addressIndex = options.addressIndex;
if (purpose == null)
purpose = 44;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
if (chain == null)
chain = options.change ? 1 : 0;
return 'm/' + purpose + '\'/'
+ coinType + '\'/'
+ accountIndex + '\'/' + accountIndex + '\'/'
+ (isChange ? '1' : '0') + '/' + chain + '/'
+ addressIndex; + addressIndex;
}; };
@ -638,40 +663,13 @@ function HDPublicKey(options) {
this.master = options.master; this.master = options.master;
} }
this.purpose = options.purpose != null
? options.purpose
: 44;
this.coinType = options.coinType != null
? options.coinType
: (network.type === 'main' ? 0 : 1);
this.accountIndex = options.accountIndex != null
? options.accountIndex
: 0;
this.isChange = options.isChange != null
? options.isChange
: false;
// this.addressIndex = options.addressIndex != null
// ? options.addressIndex
// : 0;
this.isPublic = true; this.isPublic = true;
} }
HDPublicKey.prototype.__defineGetter__('addressIndex', function() { HDPublicKey.prototype.deriveBIP44 = HDPrivateKey.prototype.deriveBIP44;
var index = this.childIndex;
assert(index < contants.hd.hardened);
return index;
});
HDPublicKey.prototype._deriveBIP44 = HDPrivateKey.prototype._deriveBIP44;
HDPublicKey.prototype.deriveAccount = HDPrivateKey.prototype.deriveAccount; HDPublicKey.prototype.deriveAccount = HDPrivateKey.prototype.deriveAccount;
HDPublicKey.prototype.deriveChange = HDPrivateKey.prototype.deriveChange; HDPublicKey.prototype.deriveChange = HDPrivateKey.prototype.deriveChange;
HDPublicKey.prototype.deriveAddress = HDPrivateKey.prototype.deriveAddress; HDPublicKey.prototype.deriveAddress = HDPrivateKey.prototype.deriveAddress;
HDPublicKey.prototype.toPath = HDPrivateKey.prototype.toPath;
HDPublicKey.isExtended = function isExtended(data) { HDPublicKey.isExtended = function isExtended(data) {
if (typeof data !== 'string') if (typeof data !== 'string')

View File

@ -264,6 +264,23 @@ Input.prototype.getID = function getID() {
return '[' + this.type + ':' + hash.slice(0, 7) + ']'; return '[' + this.type + ':' + hash.slice(0, 7) + ']';
}; };
Input.prototype.getLocktime = function getLocktime() {
var output, redeem, lock, type;
assert(this.prevout.tx);
output = this.prevout.tx.outputs[this.prevout.index];
redeem = output.script;
if (bcoin.script.isScripthash(redeem))
redeem = bcoin.script.getRedeem(this.script);
if (redeem[1] !== 'checklocktimeverify')
return;
return bcoin.script.getLocktime(redeem);
};
Input.prototype.inspect = function inspect() { Input.prototype.inspect = function inspect() {
var output = this.output var output = this.output
? this.output.inspect() ? this.output.inspect()

View File

@ -1254,22 +1254,27 @@ script._lockTime = function _lockTime(s) {
return; return;
for (i = 0; i < s.length; i++) { for (i = 0; i < s.length; i++) {
if (Array.isArray(s[i]) && s[i + 1] === 'checklocktimeverify') if (utils.isBuffer(s[i]) && s[i + 1] === 'checklocktimeverify')
return s[i]; return s[i];
} }
}; };
script.isLockTime = function isLockTime(s) { script.isLockTime = function isLockTime(s) {
return script._lockTime(s) != null; return !!script._lockTime(s);
}; };
script.getLockTime = function getLockTime(s) { script.getLockTime = function getLockTime(s) {
var lockTime = script._lockTime(s); var lockTime = script._lockTime(s);
if (!lockTime) if (!lockTime)
return 0; return;
return script.num(lockTime, true); lockTime = script.num(lockTime, true);
if (lockTime < constants.locktimeThreshold)
return { type: 'height', value: lockTime };
return { type: 'time', value: lockTime };
}; };
script.getInputData = function getData(s, prev) { script.getInputData = function getData(s, prev) {

View File

@ -548,6 +548,82 @@ TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) {
return input.script; return input.script;
}; };
TX.prototype.isSigned = function isSigned(index, required) {
var i, input, s, len, m, j, total;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (index != null && i !== index)
continue;
// We can't check for signatures unless
// we have the previous output.
assert(input.prevout.tx);
// Get the prevout's subscript
s = input.prevout.tx.getSubscript(input.prevout.index);
// Script length, needed for multisig
len = input.script.length;
// Grab the redeem script if P2SH
if (bcoin.script.isScripthash(s)) {
s = bcoin.script.getRedeem(input.script);
// Decrement `len` to avoid the redeem script
len--;
}
// Check for signatures.
// P2PK
if (bcoin.script.isPubkey(s)) {
if (!bcoin.script.isSignature(input.script[0]))
return false;
continue;
}
// P2PK
if (bcoin.script.isPubkeyhash(s)) {
if (!bcoin.script.isSignature(input.script[0]))
return false;
continue;
}
// Multisig
if (bcoin.script.isMultisig(s)) {
// Grab `m` value (number of required sigs).
m = s[0];
if (Array.isArray(m))
m = m[0] || 0;
// Ensure all members are signatures.
for (j = 1; j < len; j++) {
if (!bcoin.script.isSignature(input.script[j]))
return false;
}
// Ensure we have the correct number
// of required signatures.
if (len - 1 !== m)
return false;
continue;
}
// Unknown
total = 0;
for (j = 0; j < input.script.length; j++) {
if (bcoin.script.isSignatureEncoding(input.script[j]))
total++;
}
if (total !== required)
return false;
}
return true;
};
TX.prototype.addOutput = function addOutput(obj, value) { TX.prototype.addOutput = function addOutput(obj, value) {
var options, output; var options, output;
@ -1170,6 +1246,34 @@ TX.prototype.getFunds = function getFunds(side) {
// Legacy // Legacy
TX.prototype.funds = TX.prototype.getFunds; TX.prototype.funds = TX.prototype.getFunds;
TX.prototype.getTargetTime = function getTargetTime() {
var bestValue = 0;
var i, lockTime, bestType;
for (i = 0; i < this.inputs.length; i++) {
lockTime = this.inputs[i].getLocktime();
if (!lockTime)
continue;
// Incompatible types
if (bestType && bestType !== lockTime.type)
return;
bestType = lockTime.type;
if (lockTime.value < bestValue)
continue;
bestValue = lockTime.value;
}
return {
type: bestType || 'height',
value: bestValue
};
};
TX.prototype.testInputs = function testInputs(addressTable, index, collect) { TX.prototype.testInputs = function testInputs(addressTable, index, collect) {
var inputs = []; var inputs = [];
var i, input, prev, data, j; var i, input, prev, data, j;

View File

@ -19,6 +19,8 @@ var network = bcoin.protocol.network;
*/ */
function Wallet(options) { function Wallet(options) {
var i;
if (!(this instanceof Wallet)) if (!(this instanceof Wallet))
return new Wallet(options); return new Wallet(options);
@ -29,21 +31,44 @@ function Wallet(options) {
this.options = options; this.options = options;
this.addresses = []; this.addresses = [];
this.master = null; this.master = options.master || null;
this._addressTable = {}; this._addressTable = {};
this._labelMap = {};
this.accountIndex = options.accountIndex || 0; this.accountIndex = options.accountIndex || 0;
this.addressIndex = options.addressIndex || 0; this.addressIndex = options.addressIndex || 0;
this.changeIndex = options.changeIndex || 0; this.changeIndex = options.changeIndex || 0;
if (options.addresses) { if (options.addresses && options.addresses.length > 0) {
options.addresses.forEach(function(address) { options.addresses.forEach(function(address) {
this.addAddress(address); this.addAddress(address);
}, this); }, this);
} else { } else {
this.addAddress(options); this.createNewAddress(options);
} }
// Create a non-master account address if we don't have one.
// Might not be necessary now.
if (this.master) {
for (i = 0; i < this.addresses.length; i++) {
if (this.addresses[i].key.hd && !this.addresses[i].change)
break;
}
if (i === this.addresses.length)
this.createNewAddress(options);
}
// Find the last change address if there is one.
for (i = this.addresses.length - 1; i >= 0; i--) {
if (this.addresses[i].change)
break;
}
if (i === -1)
this.changeAddress = this.createChangeAddress();
else
this.changeAddress = this.addresses[i];
this.storage = options.storage; this.storage = options.storage;
this.label = options.label || ''; this.label = options.label || '';
this.loaded = false; this.loaded = false;
@ -77,6 +102,10 @@ Wallet.prototype._init = function init() {
}); });
this.tx.on('tx', function(tx) { this.tx.on('tx', function(tx) {
// TX using this change address was
// confirmed. Allocate a new change address.
if (self.changeAddress.ownOutput(tx))
self.changeAddress = self.createChangeAddress();
self.emit('tx', tx); self.emit('tx', tx);
}); });
@ -129,25 +158,29 @@ Wallet.prototype._addressIndex = function _addressIndex(address) {
return -1; return -1;
}; };
Wallet.prototype.createChangeAddress = function createChangeAddress(address) { Wallet.prototype.createChangeAddress = function createChangeAddress(options) {
if (!options)
options = {};
options.change = true;
if (this.master) {
options.priv =
this.master.key.hd.deriveChange(this.accountIndex, this.changeIndex++);
}
return this.addAddress(options);
};
Wallet.prototype.createNewAddress = function createNewAddress(options) {
if (!options) if (!options)
options = {}; options = {};
if (this.master) { if (this.master) {
options.change = true; options.priv =
options.priv = this.master.deriveAddress(this.addressIndex++, this.accountIndex); this.master.key.hd.deriveAddress(this.accountIndex, this.addressIndex++);
} }
return this.addAddress({ change: true });
};
Wallet.prototype.createNewAddress = function createNewAddress(address, options) {
if (!options)
options = {};
if (this.master)
options.priv = this.master.deriveAddress(this.addressIndex++, this.accountIndex);
return this.addAddress(options); return this.addAddress(options);
}; };
@ -174,6 +207,12 @@ Wallet.prototype.addAddress = function addAddress(address) {
if (this._addressIndex(address) !== -1) if (this._addressIndex(address) !== -1)
return; return;
if (address.key.hd && address.key.hd.isMaster) {
assert(!this.master);
this.master = address;
return;
}
if (address._wallet) if (address._wallet)
address._wallet.removeAddress(address); address._wallet.removeAddress(address);
@ -181,14 +220,10 @@ Wallet.prototype.addAddress = function addAddress(address) {
index = this.addresses.push(address) - 1; index = this.addresses.push(address) - 1;
if (address.hd && address.hd.isMaster && adress.hd.isPrivate) {
assert(!this.master);
this.master = address;
}
address.on('scriptaddress', address._onUpdate = function(old, cur) { address.on('scriptaddress', address._onUpdate = function(old, cur) {
self._addressTable[cur] = self._addressTable[old]; self._addressTable[cur] = self._addressTable[old];
delete self._addressTable[old]; delete self._addressTable[old];
self.emit('add address', address);
}); });
if (address.type === 'scripthash') if (address.type === 'scripthash')
@ -236,10 +271,6 @@ Wallet.prototype.removeKey = function removeKey(key) {
return this.address.removeKey(key); return this.address.removeKey(key);
}; };
Wallet.prototype.derive = function derive() {
this.addAddress(this.address.derive.apply(this.address, arguments));
};
Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
return this.address.getPrivateKey(enc); return this.address.getPrivateKey(enc);
}; };
@ -286,12 +317,33 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
}; };
Wallet.prototype.fill = function fill(tx, address, fee) { Wallet.prototype.fill = function fill(tx, address, fee) {
var result; var unspent, items, result;
if (!address) if (!address)
address = this.createChangeAddress(); address = this.changeAddress.getKeyAddress();
result = tx.fill(this.getUnspent(), address, fee); unspent = this.getUnspent();
// Avoid multisig if first address is not multisig
items = unspent.filter(function(item) {
var output = item.tx.outputs[item.index];
if (bcoin.script.isScripthash(output.script)) {
if (this.address.type === 'scripthash')
return true;
return false;
}
if (bcoin.script.isMultisig(output.script)) {
if (this.address.n > 1)
return true;
return false;
}
return true;
}, this);
if (tx.getInputs(unspent, address, fee).inputs)
unspent = items;
result = tx.fill(unspent, address, fee);
if (!result.inputs) if (!result.inputs)
return false; return false;
@ -310,23 +362,62 @@ Wallet.prototype.fillPrevout = function fillPrevout(tx) {
// Legacy // Legacy
Wallet.prototype.fillTX = Wallet.prototype.fillPrevout; Wallet.prototype.fillTX = Wallet.prototype.fillPrevout;
Wallet.prototype.createTX = function createTX(outputs, fee) {
var tx = bcoin.tx();
var target;
if (!Array.isArray(outputs))
outputs = [outputs];
outputs.forEach(function(output) {
tx.addOutput(output);
});
if (!this.fill(tx, null, fee))
return;
// Find the necessary locktime if there is
// a checklocktimeverify script in the unspents.
target = tx.getTargetTime();
// No target value. The unspents have an
// incompatible locktime type.
if (!target)
return;
if (target.value > 0)
tx.setLockTime(target.value);
else
tx.avoidFeeSnipping();
this.sign(tx);
return tx;
};
Wallet.prototype.scriptInputs = function scriptInputs(tx) { Wallet.prototype.scriptInputs = function scriptInputs(tx) {
var self = this;
return this.addresses.reduce(function(total, address) { return this.addresses.reduce(function(total, address) {
var pub = address.getPublicKey(); var pub = address.getPublicKey();
var redeem = address.getScript(); var redeem = address.getScript();
tx.inputs.forEach(function(input, i) {
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
if (!input.prevout.tx || !this.ownOutput(input.prevout.tx)) tx.inputs.forEach(function(input, i) {
if (!input.prevout.tx && self.tx._all[input.prevout.hash])
input.prevout.tx = self.tx._all[input.prevout.hash];
if (!input.prevout.tx)
return;
if (!address.ownOutput(input.prevout.tx, input.prevout.index))
return; return;
if (tx.scriptInput(i, pub, redeem)) if (tx.scriptInput(i, pub, redeem))
total++; total++;
}, this); });
return total; return total;
}, 0, this); }, 0);
}; };
Wallet.prototype.signInputs = function signInputs(tx, type) { Wallet.prototype.signInputs = function signInputs(tx, type) {
@ -340,7 +431,10 @@ Wallet.prototype.signInputs = function signInputs(tx, type) {
if (!input.prevout.tx && self.tx._all[input.prevout.hash]) if (!input.prevout.tx && self.tx._all[input.prevout.hash])
input.prevout.tx = self.tx._all[input.prevout.hash]; input.prevout.tx = self.tx._all[input.prevout.hash];
if (!input.prevout.tx || !self.ownOutput(input.prevout.tx)) if (!input.prevout.tx)
return;
if (!address.ownOutput(input.prevout.tx, input.prevout.index))
return; return;
if (tx.signInput(i, address.key, type)) if (tx.signInput(i, address.key, type))
@ -368,7 +462,10 @@ Wallet.prototype.sign = function sign(tx, type) {
input.prevout.tx = self.tx._all[input.prevout.hash]; input.prevout.tx = self.tx._all[input.prevout.hash];
// Filter inputs that this wallet own // Filter inputs that this wallet own
if (!input.prevout.tx || !self.ownOutput(input.prevout.tx)) if (!input.prevout.tx)
return;
if (!address.ownOutput(input.prevout.tx, input.prevout.index))
return; return;
if (tx.scriptSig(i, key, pub, redeem, type)) if (tx.scriptSig(i, key, pub, redeem, type))
@ -445,8 +542,9 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
accountIndex: this.accountIndex, accountIndex: this.accountIndex,
addressIndex: this.addressIndex, addressIndex: this.addressIndex,
changeIndex: this.changeIndex, changeIndex: this.changeIndex,
master: this.master ? this.master.toJSON(encrypt) : null,
addresses: this.addresses.filter(function(address) { addresses: this.addresses.filter(function(address) {
if (!address.hd) if (!address.key.hd)
return true; return true;
if (address.change) if (address.change)
@ -475,6 +573,9 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
accountIndex: json.accountIndex, accountIndex: json.accountIndex,
addressIndex: json.addressIndex, addressIndex: json.addressIndex,
changeIndex: json.changeIndex, changeIndex: json.changeIndex,
master: json.master
? bcoin.address.fromJSON(json.master, decrypt)
: null,
addresses: json.addresses.map(function(address) { addresses: json.addresses.map(function(address) {
return bcoin.address.fromJSON(address, decrypt); return bcoin.address.fromJSON(address, decrypt);
}) })
@ -483,12 +584,14 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
w.tx.fromJSON(json.tx); w.tx.fromJSON(json.tx);
// Make sure we have all the change // Make sure we have all the change
// addresses (we don't save them) // addresses (we don't save them).
for (i = 0; i < w.changeIndex; i++) { if (w.master) {
w.addKey({ for (i = 0; i < w.changeIndex; i++) {
change: true, w.addAddress({
key: w.master.deriveChange(i, w.accountIndex) change: true,
}); key: w.master.key.hd.deriveChange(w.accountIndex, i)
});
}
} }
return w; return w;

View File

@ -263,55 +263,39 @@ describe('Wallet', function() {
}); });
it('should verify 2-of-3 p2sh tx', function(cb) { it('should verify 2-of-3 p2sh tx', function(cb) {
var hd = bcoin.hd.priv();
var hd1 = hd.derive(0);
var hd2 = hd.derive(1);
var hd3 = hd.derive(2);
// Generate 3 key pairs
var key1 = bcoin.ecdsa.genKeyPair();
var key2 = bcoin.ecdsa.genKeyPair();
var key3 = bcoin.ecdsa.genKeyPair();
// var key1 = hd1;
// var key2 = hd2;
// var key3 = hd3;
// Grab the 3 pubkeys
var pub1 = key1.getPublic(true, 'array');
var pub2 = key2.getPublic(true, 'array');
var pub3 = key3.getPublic(true, 'array');
// Create 3 2-of-3 wallets with our pubkeys as "shared keys" // Create 3 2-of-3 wallets with our pubkeys as "shared keys"
var w1 = bcoin.wallet({ var w1 = bcoin.wallet({
key: key1,
multisig: { multisig: {
type: 'scripthash', type: 'scripthash',
keys: [pub2, pub3],
m: 2, m: 2,
n: 3 n: 3
} }
}); });
var w2 = bcoin.wallet({ var w2 = bcoin.wallet({
key: key2,
multisig: { multisig: {
type: 'scripthash', type: 'scripthash',
keys: [pub1, pub3],
m: 2, m: 2,
n: 3 n: 3
} }
}); });
var w3 = bcoin.wallet({ var w3 = bcoin.wallet({
key: key3, hd: true,
multisig: { multisig: {
type: 'scripthash', type: 'scripthash',
keys: [pub1, pub2],
m: 2, m: 2,
n: 3 n: 3
} }
}); });
var receive = bcoin.wallet(); var receive = bcoin.wallet();
w1.addKey(w2.getPublicKey());
w1.addKey(w3.getPublicKey());
w2.addKey(w1.getPublicKey());
w2.addKey(w3.getPublicKey());
w3.addKey(w1.getPublicKey());
w3.addKey(w2.getPublicKey());
// Our p2sh address // Our p2sh address
var addr = w1.getAddress(); var addr = w1.getAddress();
assert.equal(w1.getAddress(), addr); assert.equal(w1.getAddress(), addr);