refactor. isSigned.

This commit is contained in:
Christopher Jeffrey 2016-03-21 02:14:44 -07:00
parent 0e15723acd
commit 93efe376a6
4 changed files with 193 additions and 104 deletions

View File

@ -11,6 +11,7 @@ var utils = require('./utils');
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var Script = bcoin.script;
var Witness = bcoin.script.witness;
/**
* MTX
@ -197,7 +198,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) {
prev = addr.script;
} else if (addr.program.code[1].length === 20) {
// P2WPKH nested within pay-to-scripthash.
prev = bcoin.script.createPubkeyhash(addr.keyHash);
prev = Script.createPubkeyhash(addr.keyHash);
} else {
assert(false, 'Unknown program data length passed to address.');
}
@ -230,7 +231,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) {
if (!utils.isEqual(prev.code[1], addr.keyHash))
return false;
prev = bcoin.script.createPubkeyhash(prev.code[1]);
prev = Script.createPubkeyhash(prev.code[1]);
} else {
// Bare... who knows?
return false;
@ -298,7 +299,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) {
// Fill script with `n` signature slots.
for (i = 0; i < prev.code.length; i++) {
if (bcoin.script.isKey(prev.code[i]))
if (Script.isKey(prev.code[i]))
vector[i + 1] = dummy;
}
}
@ -333,10 +334,10 @@ MTX.prototype.createSignature = function createSignature(index, prev, key, type,
hash = this.signatureHash(index, prev, type, version);
// Sign the transaction with our one input
signature = bcoin.script.sign(hash, key, type);
signature = Script.sign(hash, key, type);
// Something is broken if this doesn't work:
// assert(bcoin.script.checksig(hash, signature, key), 'BUG: Verify failed.');
// assert(Script.checksig(hash, signature, key), 'BUG: Verify failed.');
return signature;
};
@ -383,7 +384,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
dummy = new Buffer([]);
version = 1;
} else if (prev.isWitnessPubkeyhash()) {
prev = bcoin.script.createPubkeyhash(prev.code[1]);
prev = Script.createPubkeyhash(prev.code[1]);
vector = input.witness.items;
len = vector.length;
dummy = new Buffer([]);
@ -398,7 +399,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
// P2PK
// Already signed.
if (bcoin.script.isSignature(vector[0]))
if (Script.isSignature(vector[0]))
return true;
// Make sure the pubkey is ours.
@ -414,7 +415,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
// P2PKH
// Already signed.
if (bcoin.script.isSignature(vector[0]))
if (Script.isSignature(vector[0]))
return true;
// Make sure the pubkey hash is ours.
@ -447,7 +448,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
keys = [];
for (i = 0; i < prev.code.length; i++) {
if (bcoin.script.isKey(prev.code[i]))
if (Script.isKey(prev.code[i]))
keys.push(prev.code[i]);
}
@ -464,7 +465,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
// Count the number of current signatures.
signatures = 0;
for (i = 1; i < len; i++) {
if (bcoin.script.isSignature(vector[i]))
if (Script.isSignature(vector[i]))
signatures++;
}
@ -500,7 +501,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
// and increment the total number of
// signatures.
if (ki < len && signatures < m) {
if (bcoin.script.isZero(vector[ki])) {
if (Script.isZero(vector[ki])) {
vector[ki] = signature;
signatures++;
}
@ -510,7 +511,7 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
if (signatures >= m) {
// Remove empty slots left over.
for (i = len - 1; i >= 1; i--) {
if (bcoin.script.isZero(vector[i])) {
if (Script.isZero(vector[i])) {
vector.splice(i, 1);
len--;
}
@ -534,6 +535,84 @@ MTX.prototype.signInput = function signInput(index, addr, type) {
return signatures === m;
};
MTX.prototype.isSigned = function isSigned(m) {
var i, input, prev, vector, len, j;
var total = 0;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
// We can't check for signatures unless
// we have the previous output.
if (!input.output)
return false;
// Get the prevout's subscript
prev = input.output.script;
// Script length, needed for multisig
vector = input.script.code;
len = vector.length;
// We need to grab the redeem script when
// signing p2sh transactions.
if (prev.isScripthash()) {
prev = input.script.getRedeem();
len = vector.length - 1;
}
// If the output script is a witness program,
// we have to switch the vector to the witness
// and potentially alter the length. Note that
// witnesses are stack items, so the `dummy`
// _has_ to be an empty buffer (what OP_0
// pushes onto the stack).
if (prev.isWitnessScripthash()) {
prev = input.witness.getRedeem();
vector = input.witness.items;
len = vector.length - 1;
} else if (prev.isWitnessPubkeyhash()) {
prev = Script.createPubkeyhash(prev.code[1]);
vector = input.witness.items;
len = vector.length;
}
if (prev.isPubkey()) {
if (!Script.isSignature(vector[0]))
return false;
} else if (prev.isPubkeyhash()) {
if (!Script.isSignature(vector[0]))
return false;
} else if (prev.isMultisig()) {
// Grab `m` value (number of required sigs).
m = prev[0];
if (Array.isArray(m))
m = m[0] || 0;
// Ensure all members are signatures.
for (j = 1; j < len; j++) {
if (!Script.isSignature(vector[j]))
return false;
}
// Ensure we have the correct number
// of required signatures.
if (len - 1 !== m)
return false;
} else {
for (j = 0; j < vector.length; j++) {
if (Script.isSignatureEncoding(vector[j]))
total++;
}
if (total !== m)
return false;
}
}
return true;
};
MTX.prototype.sign = function sign(index, addr, type) {
var input;
@ -582,7 +661,7 @@ MTX.prototype.addOutput = function addOutput(obj, value) {
};
MTX.prototype.scriptOutput = function scriptOutput(index, options) {
var output, script, keys, m, n, hash, flags, address;
var output, script, keys, m, n, hash, flags, address, redeem;
if (options instanceof bcoin.output)
return;
@ -593,70 +672,10 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) {
output = this.outputs[index];
assert(output);
script = output.script;
if (options.keys) {
// Bare Multisig Transaction
// https://github.com/bitcoin/bips/blob/master/bip-0010.mediawiki
// https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki
// https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki
// m [key1] [key2] ... n checkmultisig
keys = options.keys.map(utils.ensureBuffer);
m = options.m;
n = options.n || keys.length;
if (!(m >= 1 && m <= n))
return;
if (!(n >= 1 && n <= (options.scriptHash ? 15 : 3)))
return;
script = bcoin.script.createMultisig(keys, m, n);
} else if (options.address) {
address = bcoin.address.parse(options.address);
if (!address)
throw new Error(options.address + ' is not a valid address.');
if (address.type === 'pubkeyhash')
script = bcoin.script.createPubkeyhash(address.hash);
else if (address.type === 'scripthash')
script = bcoin.script.createScripthash(address.hash);
else if (address.version !== -1)
script = bcoin.script.createWitnessProgram(address.version, address.hash);
else
throw new Error('Cannot parse address: ' + options.address);
} else if (options.key) {
// P2PK Transaction
// [pubkey] checksig
script = bcoin.script.createPubkey(utils.ensureBuffer(options.key));
} else if (options.flags) {
// Nulldata Transaction
// return [data]
flags = options.flags;
if (typeof flags === 'string')
flags = new Buffer(flags, 'ascii');
assert(Buffer.isBuffer(flags));
assert(flags.length <= constants.script.maxOpReturn);
script = bcoin.script.createNulldata(flags);
}
// P2SH Transaction
// hash160 [hash] eq
if (options.scriptHash) {
if (options.locktime != null) {
script = new Script([
bcoin.script.array(options.locktime),
'checklocktimeverify',
'drop'
].concat(script.code));
}
hash = utils.ripesha(bcoin.script.encode(script.code));
script = bcoin.script.createScripthash(hash);
}
output.script = script;
if (options.script)
output.script = options.script;
else
output.script = Script.createOutputScript(options);
};
MTX.prototype.maxSize = function maxSize(maxM, maxN) {
@ -667,7 +686,7 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
// Create copy with 0-script inputs
for (i = 0; i < copy.inputs.length; i++) {
copy.inputs[i].script = new Script([]);
copy.inputs[i].witness = new bcoin.script.witness([]);
copy.inputs[i].witness = new Witness([]);
}
total = copy.render().length;
@ -704,7 +723,7 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
prev = this.inputs[i].witness.getRedeem();
size += utils.sizeIntv(prev.getSize()) + prev.getSize();
} else if (prev.isWitnessPubkeyhash()) {
prev = bcoin.script.createPubkeyhash(prev.code[1]);
prev = Script.createPubkeyhash(prev.code[1]);
}
}
@ -757,7 +776,7 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) {
} else {
// OP_PUSHDATA0 [signature]
for (j = 0; j < prev.code.length; j++) {
if (bcoin.script.isKey(prev.code[j]))
if (Script.isKey(prev.code[j]))
size += 1 + 73;
}
}
@ -887,16 +906,25 @@ MTX.prototype.selectCoins = function selectCoins(unspent, options) {
change = tx.getInputValue().sub(total());
// Attempt to subtract fee.
if (options.subtractFee) {
for (i = 0; i < tx.outputs.length; i++) {
if (tx.outputs[i].value.cmp(fee.addn(dustThreshold)) >= 0) {
if (options.subtractFee != null) {
if (typeof options.subtractFee === 'number') {
i = options.subtractFee;
assert(tx.outputs[i], 'Subtraction index does not exist.');
if (tx.outputs[i].value.cmp(fee.addn(dustThreshold)) >= 0)
tx.outputs[i].value.isub(fee);
break;
else
chosen = null;
} else {
for (i = 0; i < tx.outputs.length; i++) {
if (tx.outputs[i].value.cmp(fee.addn(dustThreshold)) >= 0) {
tx.outputs[i].value.isub(fee);
break;
}
}
// Could not subtract fee
if (i === tx.outputs.length)
chosen = null;
}
// Could not subtract fee
if (i === tx.outputs.length)
chosen = null;
}
}
@ -968,11 +996,9 @@ MTX.prototype.sortMembers = function sortMembers() {
this.inputs = this.inputs.slice().sort(function(a, b) {
var h1 = new Buffer(a.prevout.hash, 'hex');
var h2 = new Buffer(b.prevout.hash, 'hex');
var res = utils.cmp(h1, h2);
if (res !== 0)
return res;
return a.prevout.index - b.prevout.index;
});
@ -980,11 +1006,7 @@ MTX.prototype.sortMembers = function sortMembers() {
var res = a.value.cmp(b.value);
if (res !== 0)
return res;
a = a.encode();
b = b.encode();
return utils.cmp(a, b);
return utils.cmp(a.encode(), b.encode());
});
if (this.changeIndex !== -1) {
@ -1032,10 +1054,10 @@ MTX.prototype.avoidFeeSniping = function avoidFeeSniping(height) {
if (height === -1)
height = 0;
this.setLocktime(height);
if ((Math.random() * 10 | 0) === 0)
this.setLocktime(Math.max(0, this.locktime - (Math.random() * 100 | 0)));
this.setLocktime(Math.max(0, height - (Math.random() * 100 | 0)));
else
this.setLocktime(height);
};
MTX.prototype.setLocktime = function setLocktime(locktime) {

View File

@ -17,7 +17,7 @@ function Witness(items) {
if (Buffer.isBuffer(items))
this.items = Witness.decode(items);
else
else if (items)
this.items = items || [];
this.redeem = null;
@ -438,8 +438,13 @@ Script.prototype.getSubscript = function getSubscript(lastSep) {
res.push(this.code[i]);
}
if (res.length === this.code.length)
return this;
// Optimization: avoid re-rendering
// of the script in 99.9% of cases.
if (res.length === this.code.length) {
res = this.clone();
res.raw = this.raw;
return res;
}
return new Script(res);
};
@ -1685,6 +1690,67 @@ Script.getInputType = function getInputType(code, prev, isWitness) {
return type;
};
Script.createOutputScript = function(options) {
var script, keys, m, n, hash, flags, address, redeem;
if (!options)
options = {};
if (options.keys) {
keys = options.keys.map(utils.ensureBuffer);
m = options.m;
n = options.n || keys.length;
assert(m >= 1 && m <= n, 'm must be between 1 and n');
assert(
n >= 1 && n <= (options.scriptHash ? 15 : 3),
'n must be between 1 and 15');
script = Script.createMultisig(keys, m, n);
} else if (options.address) {
address = bcoin.address.parse(options.address);
if (!address)
throw new Error(options.address + ' is not a valid address.');
if (address.type === 'pubkeyhash')
script = Script.createPubkeyhash(address.hash);
else if (address.type === 'scripthash')
script = Script.createScripthash(address.hash);
else if (address.version !== -1)
script = Script.createWitnessProgram(address.version, address.hash);
else
throw new Error('Cannot parse address: ' + options.address);
} else if (options.key) {
script = Script.createPubkey(utils.ensureBuffer(options.key));
} else if (options.flags) {
flags = options.flags;
if (typeof flags === 'string')
flags = new Buffer(flags, 'ascii');
assert(Buffer.isBuffer(flags));
assert(flags.length <= constants.script.maxOpReturn);
script = Script.createNulldata(flags);
}
if (options.scriptHash) {
if (options.locktime != null) {
script = new Script([
Script.array(options.locktime),
'checklocktimeverify',
'drop'
].concat(script.code));
}
redeem = script;
hash = utils.ripesha(script.encode());
script = Script.createScripthash(hash);
script.redeem = redeem;
}
return script;
};
Script.prototype.isPubkeyInput = function isPubkeyInput(key) {
return Script.isPubkeyInput(this.code, key);
};

View File

@ -412,7 +412,7 @@ function binaryInsert(list, item, compare, search) {
utils.binaryInsert = binaryInsert;
utils.isEqual = function isEqual(a, b) {
var i = 0;
var i;
if (!a || !b)
return false;
@ -420,9 +420,10 @@ utils.isEqual = function isEqual(a, b) {
if (a.length !== b.length)
return false;
for (; i < a.length; i++)
for (i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
};

View File

@ -30,8 +30,8 @@ describe('Utils', function() {
assert.equal(btc, '546.0');
btc = utils.toBTC(new Buffer(new bn(5460).mul(new bn(10000000)).toArray()));
assert.equal(btc, '546.0');
btc = utils.toBTC(new bn(5460).mul(new bn(10000000)).toString('hex'));
assert.equal(btc, '546.0');
// btc = utils.toBTC(new bn(5460).mul(new bn(10000000)).toString('hex'));
// assert.equal(btc, '546.0');
});
it('should convert btc to satoshi', function() {