createTX. hd fixes.
This commit is contained in:
parent
42e17bc0f2
commit
156601e40a
20
README.md
20
README.md
@ -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
|
||||
|
||||
### Objects
|
||||
|
||||
@ -109,6 +109,7 @@ Address.prototype.setRedeem = function setRedeem(redeem) {
|
||||
|
||||
Address.prototype.addKey = function addKey(key) {
|
||||
var old = this.getScriptAddress();
|
||||
var cur;
|
||||
|
||||
key = utils.toBuffer(key);
|
||||
|
||||
@ -122,11 +123,16 @@ Address.prototype.addKey = function addKey(key) {
|
||||
this.keys.push(key);
|
||||
|
||||
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) {
|
||||
var old = this.getScriptAddress();
|
||||
var cur;
|
||||
|
||||
key = utils.toBuffer(key);
|
||||
|
||||
@ -142,7 +148,11 @@ Address.prototype.removeKey = function removeKey(key) {
|
||||
this.keys.splice(index, 1);
|
||||
|
||||
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) {
|
||||
|
||||
254
lib/bcoin/hd.js
254
lib/bcoin/hd.js
@ -168,153 +168,178 @@ function HDPrivateKey(options) {
|
||||
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;
|
||||
}
|
||||
|
||||
HDPrivateKey.prototype.scan = function scan(txs) {
|
||||
HDPrivateKey.prototype.scan = function scan(options, txByAddress, callback) {
|
||||
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
|
||||
var root = master
|
||||
.derive(44, true)
|
||||
.derive(network.type === 'main' ? 0 : 1, true);
|
||||
root = this
|
||||
.derive(purpose, true)
|
||||
.derive(coinType, true);
|
||||
|
||||
return (function scanner(accountIndex) {
|
||||
var account, chain, addressIndex, address, addr, i;
|
||||
var addressIndex = 0;
|
||||
var total = 0;
|
||||
var gap = 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
|
||||
chain = account.derive(0);
|
||||
var chain = account.derive(0);
|
||||
|
||||
// 3. scan addresses of the external chain;
|
||||
// respect the gap limit described below
|
||||
addressIndex = 0;
|
||||
main:
|
||||
for (;;) {
|
||||
address = chain.derive(addressIndex++);
|
||||
addr = bcoin.address.hash2addr(
|
||||
return (function next() {
|
||||
var address = chain.derive(addressIndex++);
|
||||
var addr = bcoin.address.hash2addr(
|
||||
bcoin.address.key2hash(address.publicKey),
|
||||
'pubkey');
|
||||
|
||||
for (i = 0; i < txs.length; i++) {
|
||||
txs[i].fillPrevout(txs);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return txByAddress(addr, function(err, txs) {
|
||||
var result;
|
||||
|
||||
// 5. if there are some transactions, increase
|
||||
// the account index and go to step 1
|
||||
return scanner(accountIndex + 1);
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.__defineGetter__('addressIndex', function() {
|
||||
var index = this.childIndex;
|
||||
assert(index < contants.hd.hardened);
|
||||
return index;
|
||||
});
|
||||
|
||||
HDPrivateKey.prototype._deriveBIP44 = function _deriveBIP44() {
|
||||
HDPrivateKey.prototype.deriveBIP44 = function deriveBIP44(options) {
|
||||
var purpose = options.purpose;
|
||||
var coinType = options.coinType;
|
||||
var accountIndex = options.accountIndex;
|
||||
var chain = options.chain;
|
||||
var addressIndex = options.addressIndex;
|
||||
var child;
|
||||
|
||||
assert(this.isMaster);
|
||||
if (purpose == null)
|
||||
purpose = 44;
|
||||
|
||||
if (options.purpose == null)
|
||||
options.purpose = 44;
|
||||
if (coinType == null)
|
||||
coinType = network.type === 'main' ? 0 : 1;
|
||||
|
||||
if (options.coinType == null)
|
||||
options.coinType = network.type === 'main' ? 0 : 1;
|
||||
if (chain == null)
|
||||
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
|
||||
.derive(options.purpose, true)
|
||||
.derive(options.coinType, true)
|
||||
.derive(options.accountIndex, true)
|
||||
.derive(options.isChange ? 1 : 0)
|
||||
.derive(options.addressIndex);
|
||||
|
||||
child.purpose = options.purpose;
|
||||
child.coinType = options.coinType;
|
||||
child.accountIndex = options.accountIndex;
|
||||
child.isChange = options.isChange;
|
||||
child.addressIndex = options.addressIndex;
|
||||
.derive(purpose, true)
|
||||
.derive(coinType, true)
|
||||
.derive(accountIndex, true)
|
||||
.derive(chain)
|
||||
.derive(addressIndex);
|
||||
|
||||
return child;
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.deriveAccount = function deriveAccount(accountIndex) {
|
||||
return this._deriveBIP44({
|
||||
purpose: this.purpose,
|
||||
coinType: this.coinType,
|
||||
accountIndex: accountIndex != null ? accountIndex : this.accountIndex + 1,
|
||||
isChange: false,
|
||||
return this.deriveBIP44({
|
||||
accountIndex: accountIndex,
|
||||
chain: 0,
|
||||
addressIndex: 0
|
||||
});
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.deriveChange = function deriveChange(addressIndex, accountIndex) {
|
||||
return this._deriveBIP44({
|
||||
purpose: this.purpose,
|
||||
coinType: this.coinType,
|
||||
accountIndex: accountIndex != null ? accountIndex : this.accountIndex,
|
||||
isChange: true,
|
||||
addressIndex: addressIndex != null ? addressIndex : this.addressIndex + 1
|
||||
HDPrivateKey.prototype.deriveChange = function deriveChange(accountIndex, addressIndex) {
|
||||
return this.deriveBIP44({
|
||||
accountIndex: accountIndex,
|
||||
chain: 1,
|
||||
addressIndex: addressIndex
|
||||
});
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.deriveAddress = function deriveAddress(addressIndex, accountIndex) {
|
||||
return this._deriveBIP44({
|
||||
purpose: this.purpose,
|
||||
coinType: this.coinType,
|
||||
accountIndex: accountIndex != null ? accountIndex : this.accountIndex,
|
||||
isChange: false,
|
||||
addressIndex: addressIndex != null ? addressIndex : this.addressIndex + 1
|
||||
HDPrivateKey.prototype.deriveAddress = function deriveAddress(accountIndex, addressIndex) {
|
||||
return this.deriveBIP44({
|
||||
accountIndex: accountIndex,
|
||||
chain: 0,
|
||||
addressIndex: addressIndex
|
||||
});
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.toPath = function toPath(data) {
|
||||
assert(!this.isMaster);
|
||||
return HDPrivateKey.getPath(
|
||||
this.accountIndex, this.addressIndex, this.isChange);
|
||||
};
|
||||
HDPrivateKey.getPath = function getPath(options) {
|
||||
var purpose, coinType, accountIndex, chain, addressIndex;
|
||||
|
||||
HDPrivateKey.getPath = function toPath(accountIndex, addressIndex, isChange) {
|
||||
return 'm/44\'/'
|
||||
+ (network.type === 'main' ? '0' : '1') + '\'' + '/'
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
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 + '\'/'
|
||||
+ (isChange ? '1' : '0') + '/'
|
||||
+ chain + '/'
|
||||
+ addressIndex;
|
||||
};
|
||||
|
||||
@ -638,40 +663,13 @@ function HDPublicKey(options) {
|
||||
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;
|
||||
}
|
||||
|
||||
HDPublicKey.prototype.__defineGetter__('addressIndex', function() {
|
||||
var index = this.childIndex;
|
||||
assert(index < contants.hd.hardened);
|
||||
return index;
|
||||
});
|
||||
|
||||
HDPublicKey.prototype._deriveBIP44 = HDPrivateKey.prototype._deriveBIP44;
|
||||
HDPublicKey.prototype.deriveBIP44 = HDPrivateKey.prototype.deriveBIP44;
|
||||
HDPublicKey.prototype.deriveAccount = HDPrivateKey.prototype.deriveAccount;
|
||||
HDPublicKey.prototype.deriveChange = HDPrivateKey.prototype.deriveChange;
|
||||
HDPublicKey.prototype.deriveAddress = HDPrivateKey.prototype.deriveAddress;
|
||||
HDPublicKey.prototype.toPath = HDPrivateKey.prototype.toPath;
|
||||
|
||||
HDPublicKey.isExtended = function isExtended(data) {
|
||||
if (typeof data !== 'string')
|
||||
|
||||
@ -264,6 +264,23 @@ Input.prototype.getID = function getID() {
|
||||
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() {
|
||||
var output = this.output
|
||||
? this.output.inspect()
|
||||
|
||||
@ -1254,22 +1254,27 @@ script._lockTime = function _lockTime(s) {
|
||||
return;
|
||||
|
||||
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];
|
||||
}
|
||||
};
|
||||
|
||||
script.isLockTime = function isLockTime(s) {
|
||||
return script._lockTime(s) != null;
|
||||
return !!script._lockTime(s);
|
||||
};
|
||||
|
||||
script.getLockTime = function getLockTime(s) {
|
||||
var lockTime = script._lockTime(s);
|
||||
|
||||
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) {
|
||||
|
||||
104
lib/bcoin/tx.js
104
lib/bcoin/tx.js
@ -548,6 +548,82 @@ TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) {
|
||||
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) {
|
||||
var options, output;
|
||||
|
||||
@ -1170,6 +1246,34 @@ TX.prototype.getFunds = function getFunds(side) {
|
||||
// Legacy
|
||||
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) {
|
||||
var inputs = [];
|
||||
var i, input, prev, data, j;
|
||||
|
||||
@ -19,6 +19,8 @@ var network = bcoin.protocol.network;
|
||||
*/
|
||||
|
||||
function Wallet(options) {
|
||||
var i;
|
||||
|
||||
if (!(this instanceof Wallet))
|
||||
return new Wallet(options);
|
||||
|
||||
@ -29,21 +31,44 @@ function Wallet(options) {
|
||||
|
||||
this.options = options;
|
||||
this.addresses = [];
|
||||
this.master = null;
|
||||
this.master = options.master || null;
|
||||
this._addressTable = {};
|
||||
this._labelMap = {};
|
||||
|
||||
this.accountIndex = options.accountIndex || 0;
|
||||
this.addressIndex = options.addressIndex || 0;
|
||||
this.changeIndex = options.changeIndex || 0;
|
||||
|
||||
if (options.addresses) {
|
||||
if (options.addresses && options.addresses.length > 0) {
|
||||
options.addresses.forEach(function(address) {
|
||||
this.addAddress(address);
|
||||
}, this);
|
||||
} 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.label = options.label || '';
|
||||
this.loaded = false;
|
||||
@ -77,6 +102,10 @@ Wallet.prototype._init = function init() {
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@ -129,25 +158,29 @@ Wallet.prototype._addressIndex = function _addressIndex(address) {
|
||||
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)
|
||||
options = {};
|
||||
|
||||
if (this.master) {
|
||||
options.change = true;
|
||||
options.priv = this.master.deriveAddress(this.addressIndex++, this.accountIndex);
|
||||
options.priv =
|
||||
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);
|
||||
};
|
||||
|
||||
@ -174,6 +207,12 @@ Wallet.prototype.addAddress = function addAddress(address) {
|
||||
if (this._addressIndex(address) !== -1)
|
||||
return;
|
||||
|
||||
if (address.key.hd && address.key.hd.isMaster) {
|
||||
assert(!this.master);
|
||||
this.master = address;
|
||||
return;
|
||||
}
|
||||
|
||||
if (address._wallet)
|
||||
address._wallet.removeAddress(address);
|
||||
|
||||
@ -181,14 +220,10 @@ Wallet.prototype.addAddress = function addAddress(address) {
|
||||
|
||||
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) {
|
||||
self._addressTable[cur] = self._addressTable[old];
|
||||
delete self._addressTable[old];
|
||||
self.emit('add address', address);
|
||||
});
|
||||
|
||||
if (address.type === 'scripthash')
|
||||
@ -236,10 +271,6 @@ Wallet.prototype.removeKey = function 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) {
|
||||
return this.address.getPrivateKey(enc);
|
||||
};
|
||||
@ -286,12 +317,33 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
};
|
||||
|
||||
Wallet.prototype.fill = function fill(tx, address, fee) {
|
||||
var result;
|
||||
var unspent, items, result;
|
||||
|
||||
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)
|
||||
return false;
|
||||
@ -310,23 +362,62 @@ Wallet.prototype.fillPrevout = function fillPrevout(tx) {
|
||||
// Legacy
|
||||
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) {
|
||||
var self = this;
|
||||
|
||||
return this.addresses.reduce(function(total, address) {
|
||||
var pub = address.getPublicKey();
|
||||
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;
|
||||
|
||||
if (tx.scriptInput(i, pub, redeem))
|
||||
total++;
|
||||
}, this);
|
||||
});
|
||||
|
||||
return total;
|
||||
}, 0, this);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
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])
|
||||
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;
|
||||
|
||||
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];
|
||||
|
||||
// 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;
|
||||
|
||||
if (tx.scriptSig(i, key, pub, redeem, type))
|
||||
@ -445,8 +542,9 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
|
||||
accountIndex: this.accountIndex,
|
||||
addressIndex: this.addressIndex,
|
||||
changeIndex: this.changeIndex,
|
||||
master: this.master ? this.master.toJSON(encrypt) : null,
|
||||
addresses: this.addresses.filter(function(address) {
|
||||
if (!address.hd)
|
||||
if (!address.key.hd)
|
||||
return true;
|
||||
|
||||
if (address.change)
|
||||
@ -475,6 +573,9 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
|
||||
accountIndex: json.accountIndex,
|
||||
addressIndex: json.addressIndex,
|
||||
changeIndex: json.changeIndex,
|
||||
master: json.master
|
||||
? bcoin.address.fromJSON(json.master, decrypt)
|
||||
: null,
|
||||
addresses: json.addresses.map(function(address) {
|
||||
return bcoin.address.fromJSON(address, decrypt);
|
||||
})
|
||||
@ -483,12 +584,14 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
|
||||
w.tx.fromJSON(json.tx);
|
||||
|
||||
// Make sure we have all the change
|
||||
// addresses (we don't save them)
|
||||
for (i = 0; i < w.changeIndex; i++) {
|
||||
w.addKey({
|
||||
change: true,
|
||||
key: w.master.deriveChange(i, w.accountIndex)
|
||||
});
|
||||
// addresses (we don't save them).
|
||||
if (w.master) {
|
||||
for (i = 0; i < w.changeIndex; i++) {
|
||||
w.addAddress({
|
||||
change: true,
|
||||
key: w.master.key.hd.deriveChange(w.accountIndex, i)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return w;
|
||||
|
||||
@ -263,55 +263,39 @@ describe('Wallet', function() {
|
||||
});
|
||||
|
||||
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"
|
||||
var w1 = bcoin.wallet({
|
||||
key: key1,
|
||||
multisig: {
|
||||
type: 'scripthash',
|
||||
keys: [pub2, pub3],
|
||||
m: 2,
|
||||
n: 3
|
||||
}
|
||||
});
|
||||
var w2 = bcoin.wallet({
|
||||
key: key2,
|
||||
multisig: {
|
||||
type: 'scripthash',
|
||||
keys: [pub1, pub3],
|
||||
m: 2,
|
||||
n: 3
|
||||
}
|
||||
});
|
||||
var w3 = bcoin.wallet({
|
||||
key: key3,
|
||||
hd: true,
|
||||
multisig: {
|
||||
type: 'scripthash',
|
||||
keys: [pub1, pub2],
|
||||
m: 2,
|
||||
n: 3
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
var addr = w1.getAddress();
|
||||
assert.equal(w1.getAddress(), addr);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user