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
|
## API Documentation
|
||||||
|
|
||||||
### Objects
|
### Objects
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
254
lib/bcoin/hd.js
254
lib/bcoin/hd.js
@ -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')
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
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;
|
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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user