transaction improvements.

This commit is contained in:
Christopher Jeffrey 2016-01-10 03:23:29 -08:00
parent 0b7ca266ee
commit eb1a3ea6d2
10 changed files with 167 additions and 154 deletions

View File

@ -7,6 +7,7 @@
var bn = require('bn.js');
var bcoin = require('../bcoin');
var utils = bcoin.utils;
var constants = bcoin.protocol.constants;
/**
* Input
@ -180,6 +181,7 @@ Input.prototype.__defineGetter__('scriptaddr', function() {
// Schema and defaults for data object:
// {
// type: null,
// subtype: null,
// side: 'input',
// signatures: [],
// keys: [],
@ -263,7 +265,7 @@ Input.getData = function getData(input) {
} else if (data.type === 'scripthash') {
// We work backwards here: scripthash is one of the few cases
// where we get more data from the input than the output.
val = Input.getData({
val = bcoin.input.getData({
out: { hash: input.out.hash, index: input.out.index },
script: input.script,
seq: input.seq
@ -313,19 +315,16 @@ Input.getData = function getData(input) {
}
if (bcoin.script.isScripthashInput(s)) {
signature = sub.slice(1, -1);
redeem = sub[sub.length - 1];
hash = utils.ripesha(redeem);
address = bcoin.wallet.hash2addr(hash, 'scripthash');
redeem = bcoin.script.decode(redeem);
data = bcoin.output.getData({
script: redeem,
value: new bn(0)
});
return utils.merge(data, {
val = bcoin.input.getData(sub.slice(0, -1));
data = bcoin.output.getData(redeem);
return utils.merge(val, data, {
type: 'scripthash',
subtype: data.type,
side: 'input',
signatures: signature,
redeem: redeem,
scripthash: hash,
scriptaddress: address,
@ -351,6 +350,7 @@ Input.prototype.inspect = function inspect() {
return {
type: this.type,
subtype: this.data.subtype,
address: this.address,
signatures: this.signatures.map(utils.toHex),
keys: this.keys.map(utils.toHex),
@ -358,6 +358,7 @@ Input.prototype.inspect = function inspect() {
lock: this.lock,
value: utils.btc(output.value),
script: bcoin.script.format(this.script)[0],
redeem: this.data.redeem ? bcoin.script.format(this.data.redeem)[0] : null,
seq: this.seq,
output: output
};

View File

@ -147,6 +147,7 @@ Output.prototype.__defineGetter__('scriptaddr', function() {
// Schema and defaults for data object:
// {
// type: null,
// subtype: null,
// side: 'output',
// signatures: [],
// keys: [],
@ -215,14 +216,13 @@ Output.getData = function getData(output) {
address = bcoin.wallet.hash2addr(hash);
return utils.merge(def, {
type: 'pubkeyhash',
side: 'output',
hashes: [hash],
addresses: [address]
});
}
keys = bcoin.script.isMultisig(s);
if (keys) {
if (bcoin.script.isMultisig(s)) {
keys = sub.slice(1, -2);
hash = keys.map(function(key) {
return utils.ripesha(key);
});
@ -272,9 +272,6 @@ Output.prototype.inspect = function inspect() {
keys: this.keys.map(utils.toHex),
hashes: this.hashes.map(utils.toHex),
addresses: this.addresses,
redeem: this.type === 'scripthash'
? bcoin.script.format(this.data.redeem)[0]
: null,
m: this.m,
n: this.n,
text: this.text,

View File

@ -47,7 +47,6 @@ Parser.prototype.feed = function feed(data) {
while (this.pendingTotal >= this.waiting) {
// Concat chunks
// chunk = new Array(this.waiting);
chunk = new Buffer(this.waiting);
i = 0;
@ -55,7 +54,6 @@ Parser.prototype.feed = function feed(data) {
len = 0;
for (; off < chunk.length; i++) {
// len = utils.copy(this.pending[0], chunk, off);
len = this.pending[0].copy(chunk, off, 0, this.pending[0].length);
if (len === this.pending[0].length)
this.pending.shift();

View File

@ -150,6 +150,64 @@ script.encode = function encode(s) {
return res;
};
script.verify = function verify(input, output, tx, i, flags) {
var copy, res, redeem;
var stack = [];
if (!flags)
flags = {};
// Execute the input script
script.execute(input, stack, tx, i, flags);
// Copy the stack for P2SH
if (flags.verifyp2sh !== false)
copy = stack.slice();
// Execute the previous output script
res = script.execute(output, stack, tx, i, flags);
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
// If the script is P2SH, execute the real output script
if (flags.verifyp2sh !== false && script.isScripthash(output)) {
// P2SH can only have push ops in the scriptSig
if (!script.pushOnly(input))
return false;
// Reset the stack
stack = copy;
// Stack should _never_ be empty at this point
assert(stack.length !== 0);
// Grab the real redeem script
redeem = stack.pop();
if (!Array.isArray(redeem))
return false;
redeem = script.decode(redeem);
// Execute the redeem script
res = script.execute(redeem, stack, tx, i, flags);
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
}
// Ensure there is nothing left on the stack
if (flags.cleanstack !== false) {
if (stack.length !== 0)
return false;
}
return true;
};
script.subscript = function subscript(s, lastSep) {
var i, res;
@ -179,7 +237,7 @@ script.subscript = function subscript(s, lastSep) {
return res;
};
script.verify = function verify(hash, sig, pub) {
script.checksig = function checksig(hash, sig, pub) {
var k;
try {
@ -249,7 +307,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
var n, n1, n2, n3;
var res;
var key, sig, type, subscript, hash;
var keys, i, j, key, m;
var keys, i, j, m;
var succ;
var lock, threshold;
var evalScript;
@ -700,7 +758,7 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
subscript = script.subscript(s, lastSep);
hash = tx.subscriptHash(index, subscript, type);
res = script.verify(hash, sig.slice(0, -1), key);
res = script.checksig(hash, sig.slice(0, -1), key);
if (o === 'checksigverify') {
if (!res)
return false;
@ -763,13 +821,19 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
res = false;
for (; !res && j < n; j++)
res = script.verify(hash, sig.slice(0, -1), keys[j]);
res = script.checksig(hash, sig.slice(0, -1), keys[j]);
if (res)
succ++;
}
// Extra value
if (stack.length < 1)
return false;
if (flags.verifynulldummy !== false) {
if (stack[stack.length - 1].length > 0)
return false;
}
stack.pop();
// Too many signatures on stack
@ -866,20 +930,6 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) {
return true;
};
script.exec = function exec(input, output, tx, i, flags) {
var stack = [];
var res;
script.execute(input, stack, tx, i, flags);
res = script.execute(output, stack, tx, i, flags);
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
return true;
};
script.redeem = function redeem(keys, m, n) {
if (keys.length !== n)
throw new Error(n + ' keys are required to generate redeem script');
@ -892,9 +942,9 @@ script.redeem = function redeem(keys, m, n) {
keys = utils.sortKeys(keys);
return [ m ].concat(
return [m].concat(
keys,
[ n, 'checkmultisig' ]
[n, 'checkmultisig']
);
};
@ -908,11 +958,8 @@ script.standard = function standard(s) {
};
script.isStandard = function isStandard(s) {
var m, n;
var type = script.standard(s);
if (!type)
return false;
var m, n;
if (type === 'multisig') {
m = new bn(s[0]).toNumber();
@ -982,7 +1029,7 @@ script.isPubkey = function isPubkey(s, key) {
if (key)
return utils.isEqual(s[0], key);
return s[0];
return true;
};
script.isPubkeyhash = function isPubkeyhash(s, hash) {
@ -1008,11 +1055,11 @@ script.isPubkeyhash = function isPubkeyhash(s, hash) {
if (hash)
return utils.isEqual(s[2], hash);
return s[2];
return true;
};
script.isMultisig = function isMultisig(s, pubs) {
var m, n, keys, isArray, total;
var m, n, keys, total;
s = script.subscript(s);
@ -1048,15 +1095,11 @@ script.isMultisig = function isMultisig(s, pubs) {
keys = s.slice(1, 1 + n);
isArray = keys.every(function(k) {
return Array.isArray(k);
});
if (!isArray)
if (!keys.every(Array.isArray))
return false;
if (!pubs)
return keys;
return true;
total = keys.filter(function(k) {
return pubs.some(function(pub) {
@ -1133,7 +1176,7 @@ script.isPubkeyInput = function isPubkeyInput(s, key, tx, i) {
// checksig script to see if this is our input.
// This will only work if the script verifies.
if (key)
return script.exec(s, [key, 'checksig'], tx, i);
return script.verify(s, [key, 'checksig'], tx, i);
return true;
};
@ -1153,10 +1196,10 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) {
if (key)
return utils.isEqual(s[1], key);
return s[1];
return true;
};
script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
script.isMultisigInput = function isMultisigInput(s, keys, tx, i) {
var i, res, o;
// We need to rule out scripthash
@ -1180,40 +1223,58 @@ script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
// Execute the script against our pubkeys'
// redeem script to see if this is our input.
// This will only work if the script verifies.
if (pubs && pubs.length >= 2) {
o = script.redeem(pubs, 2, pubs.length);
return script.exec(s, o, tx, i);
if (keys && keys.length >= 2) {
o = script.redeem(keys, s.length - 1, keys.length);
return script.verify(s, o, tx, i);
}
return true;
};
script.isScripthashInput = function isScripthashInput(s, redeem) {
var i, res, r, keys;
script.isScripthashInput = function isScripthashInput(s, data) {
var raw, redeem;
s = script.subscript(s);
if (s.length < 4)
// Grab the raw redeem script.
raw = s[s.length - 1];
// Need at least one data element with
// the redeem script.
if (s.length < 2)
return false;
if (!Array.isArray(s[0]) || s[0].length !== 0)
// Last data element should be an array
// for the redeem script.
if (!Array.isArray(raw))
return false;
for (i = 1; i < s.length - 1; i++) {
if (!script.isSig(s[i]))
return false;
// P2SH redeem scripts can be nonstandard: make
// it easier for other functions to parse this.
redeem = script.decode(raw);
redeem = script.subscript(redeem);
if (script.lockTime(redeem))
redeem = redeem.slice(3);
// Get the "real" scriptSig
s = s.slice(0, -1);
// Do some sanity checking on the inputs
if (!script.isPubkeyInput(s)
&& !script.isPubkeyhashInput(s)
&& !script.isMultisigInput(s)) {
return false;
}
r = Array.isArray(s[s.length - 1]) && s[s.length - 1];
if (r[r.length - 1] !== constants.opcodes.checkmultisig)
return false;
// Check data against last array in case
// a raw redeem script was passed in.
if (data && utils.isEqual(data, raw))
return true;
if (redeem)
return utils.isEqual(redeem, r);
keys = script.decode(r).slice(1, -2);
return keys;
// Test against all other script types
return script.isPubkey(redeem, data)
|| script.isPubkeyhash(redeem, data)
|| script.isMultisig(redeem, data);
};
script.coinbaseBits = function coinbaseBits(s, block) {
@ -1526,7 +1587,7 @@ script.sigopsScripthash = function sigopsScripthash(s) {
};
script.args = function args(s) {
var type, pubs, m;
var type, keys, m;
s = bcoin.script.subscript(s);
@ -1542,11 +1603,11 @@ script.args = function args(s) {
return 2;
if (type === 'multisig') {
pubs = bcoin.script.isMultisig(s);
keys = bcoin.script.isMultisig(s);
if (!pub)
return -1;
m = new bn(s[0]).toNumber();
if (pubs.length < 1 || m < 1)
if (keys.length < 1 || m < 1)
return -1;
return m + 1;
}

View File

@ -123,7 +123,6 @@ TXPool.prototype.add = function add(tx, noWrite) {
// signature checking code to ownInput for p2sh and p2pk,
// we could in theory use ownInput here (and down below)
// instead.
// if (this._wallet.ownInput(input.out.tx, input.out.index))
if (input.out.tx) {
if (!this._wallet.ownOutput(input.out.tx, input.out.index))
continue;

View File

@ -596,8 +596,11 @@ TX.prototype.verify = function verify(index, force, flags) {
if (this.inputs.length === 0)
return false;
if (!flags)
flags = {};
return this.inputs.every(function(input, i) {
var stack, prev, push, res, redeem;
var output;
if (index != null && index !== i)
return true;
@ -605,41 +608,16 @@ TX.prototype.verify = function verify(index, force, flags) {
if (!input.out.tx)
return false;
output = input.out.tx.outputs[input.out.index];
assert(input.out.tx.outputs.length > input.out.index);
assert.equal(input.out.tx.hash('hex'), input.out.hash);
// Transaction cannot reference itself
if (input.out.tx.hash('hex') === this.hash('hex'))
return false;
assert(input.out.tx.outputs.length > input.out.index);
stack = [];
prev = input.out.tx.outputs[input.out.index].script;
if (bcoin.script.isScripthash(prev)) {
// P2SH transactions cannot have anything
// other than pushdata ops in the scriptSig.
if (!bcoin.script.pushOnly(input.script))
return false;
}
bcoin.script.execute(input.script, stack, this, i, flags);
res = bcoin.script.execute(prev, stack, this, i, flags);
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
if (bcoin.script.isScripthash(prev)) {
redeem = input.script[input.script.length - 1];
if (!Array.isArray(redeem))
return false;
redeem = bcoin.script.decode(redeem);
res = bcoin.script.execute(redeem, stack, this, i, flags);
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
}
return true;
return bcoin.script.verify(input.script, output.script, this, i, flags);
}, this);
};
@ -660,7 +638,7 @@ TX.prototype.maxSize = function maxSize() {
// Add size for signatures and public keys
copy.inputs.forEach(function(input, i) {
var s, m, n, script, redeem;
var s, m, n;
// Get the previous output's subscript
s = input.out.tx.getSubscript(input.out.index);
@ -694,34 +672,16 @@ TX.prototype.maxSize = function maxSize() {
}
if (bcoin.script.isScripthash(s)) {
script = this.inputs[i].script;
if (script.length) {
redeem = bcoin.script.decode(script[script.length - 1]);
m = redeem[0];
n = redeem[redeem.length - 2];
// If using pushdata instead of OP_1-16:
if (Array.isArray(m))
m = m[0] || 0;
if (Array.isArray(n))
n = n[0] || 0;
} else {
// May end up in a higher fee if we
// do not have the redeem script available.
m = 15;
n = 15;
}
assert(m >= 1 && m <= n);
assert(n >= 1 && n <= 15);
// Multisig
// Empty byte
size += 1;
// Signature + len
size += 74 * m;
size += 74 * 15;
// Redeem script
// m byte
size += 1;
// 1 byte length + 65 byte pubkey
size += 66 * n;
size += 66 * 15;
// n byte
size += 1;
// checkmultisig byte
@ -848,7 +808,7 @@ TX.prototype._recalculateFee = function recalculateFee() {
output = this.outputs[this.outputs.length - 1];
}
var byteSize = this.maxSize();
var byteSize = this.render().length;
var newFee = Math.ceil(byteSize / 1024) * TX.fee;
var currentFee = this.getFee().toNumber();

View File

@ -366,8 +366,10 @@ utils.array2utf8 = function array2utf8(arr) {
};
utils.copy = function copy(src, dst, off, force) {
if (Buffer.isBuffer(src) && Buffer.isBuffer(dst) && !force)
if (Buffer.isBuffer(src) && Buffer.isBuffer(dst)) {
assert(!force);
return src.copy(dst, off, 0, src.length);
}
var len = src.length;
var i = 0;

View File

@ -405,7 +405,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
if (bcoin.script.isPubkeyhashInput(input.script, key))
return true;
// if (bcoin.script.isMultisigInput(input.script, key, tx, i))
// if (bcoin.script.isMultisigInput(input.script, keys, tx, i))
// return true;
if (bcoin.script.isScripthashInput(input.script, redeem))
@ -449,10 +449,9 @@ Wallet.prototype.fillUnspent = function fillUnspent(tx, changeAddress) {
return tx.fillUnspent(this.unspent(), changeAddress);
};
Wallet.prototype.scriptInputs = function scriptInputs(tx, inputs) {
Wallet.prototype.scriptInputs = function scriptInputs(tx) {
var pub = this.getFullPublicKey();
inputs = inputs || tx.inputs;
var inputs = tx.inputs;
inputs = inputs.filter(function(input, i) {
if (!input.out.tx && this.tx._all[input.out.hash])
@ -470,14 +469,13 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, inputs) {
return inputs.length;
};
Wallet.prototype.signInputs = function signInputs(tx, type, inputs) {
Wallet.prototype.signInputs = function signInputs(tx, type) {
var key = this.key;
var inputs = tx.inputs;
if (!type)
type = 'all';
inputs = inputs || tx.inputs;
inputs = inputs.filter(function(input, i) {
if (!input.out.tx && this.tx._all[input.out.hash])
input.out.tx = this.tx._all[input.out.hash];
@ -493,14 +491,13 @@ Wallet.prototype.signInputs = function signInputs(tx, type, inputs) {
return inputs.length;
};
Wallet.prototype.sign = function sign(tx, type, inputs) {
Wallet.prototype.sign = function sign(tx, type) {
if (!type)
type = 'all';
var pub = this.getFullPublicKey();
var key = this.key;
inputs = inputs || tx.inputs;
var inputs = tx.inputs;
// Add signature script to each input
inputs = inputs.filter(function(input, i) {
@ -623,11 +620,7 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
};
Wallet.fromJSON = function fromJSON(json, decrypt) {
var compressed, key, w;
var priv = json.priv;
var pub = json.pub;
var xprivkey = json.xprivkey;
var multisig = json.multisig;
var priv, pub, xprivkey, multisig, compressed, key, w;
assert.equal(json.v, 2);
assert.equal(json.type, 'wallet');
@ -638,7 +631,8 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
if (json.encrypted && !decrypt)
throw new Error('Cannot decrypt wallet');
if (priv) {
if (json.priv) {
priv = json.priv;
if (json.encrypted)
priv = decrypt(priv);
@ -656,20 +650,21 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
compressed = false;
}
} else {
pub = bcoin.utils.fromBase58(pub);
pub = bcoin.utils.fromBase58(json.pub);
compressed = pub[0] !== 0x04;
}
if (multisig) {
if (json.multisig) {
multisig = {
type: multisig.type,
keys: multisig.keys.map(utils.fromBase58),
m: multisig.m,
n: multisig.n
type: json.multisig.type,
keys: json.multisig.keys.map(utils.fromBase58),
m: json.multisig.m,
n: json.multisig.n
};
}
if (xprivkey) {
if (json.xprivkey) {
xprivkey = json.xprivkey;
if (json.encrypted)
xprivkey = decrypt(xprivkey);
priv = bcoin.hd.priv(xprivkey);

View File

@ -42,7 +42,7 @@ describe('Script', function() {
var hex = '6a28590c080112220a1b353930632e6f7267282a5f5e294f7665726c6179404f7261636c65103b1a010c'
var encoded = bcoin.utils.toArray(hex, 'hex')
var decoded = bcoin.script.decode(encoded);
assert(bcoin.script.isColored(decoded))
assert(bcoin.script.isNulldata(decoded))
})
it('should handle if statements correctly', function () {

View File

@ -254,8 +254,8 @@ describe('Wallet', function() {
tx.input(unspent1[1]);
tx.input(unspent1[2]);
tx.input(unspent2[1]);
assert.equal(w1.sign(tx, 'all', tx.inputs.slice(), 0), 2);
assert.equal(w2.sign(tx, 'all', tx.inputs.slice(2), 2), 1);
assert.equal(w1.sign(tx, 'all', tx.inputs.slice()), 2);
assert.equal(w2.sign(tx, 'all', tx.inputs.slice(2)), 1);
// Verify
assert.equal(tx.verify(), true);