diff --git a/Key.js b/Key.js index 97d958f..2cc123b 100644 --- a/Key.js +++ b/Key.js @@ -77,6 +77,10 @@ if (process.versions) { // return it as a buffer to keep c++ compatibility return new Buffer(signature); }; + + kSpec.prototype.verifySignature = function(hash, sig, callback) { + + }; kSpec.prototype.verifySignatureSync = function(hash, sig) { var self = this; diff --git a/ScriptInterpreter.js b/ScriptInterpreter.js index 5295820..63f4533 100644 --- a/ScriptInterpreter.js +++ b/ScriptInterpreter.js @@ -7,6 +7,7 @@ var buffertools = imports.buffertools || require('buffertools'); var bignum = imports.bignum || require('bignum'); var Util = imports.Util || require('./util/util'); var Script = require('./Script'); +var Key = require('./Key'); var SIGHASH_ALL = 1; var SIGHASH_NONE = 2; @@ -61,724 +62,703 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, return; } - try { - // The execution bit is true if there are no "false" values in the - // execution stack. (A "false" value indicates that we're in the - // inactive branch of an if statement.) - var exec = !~execStack.indexOf(false); + // The execution bit is true if there are no "false" values in the + // execution stack. (A "false" value indicates that we're in the + // inactive branch of an if statement.) + var exec = !~execStack.indexOf(false); - var opcode = script.chunks[pc++]; + var opcode = script.chunks[pc++]; - if (opcode.length > 520) { - throw new Error("Max push value size exceeded (>520)"); - } - - if (opcode > OP_16 && ++opCount > 201) { - throw new Error("Opcode limit exceeded (>200)"); - } - - if (this.disableUnsafeOpcodes && - "number" === typeof opcode && - (opcode === OP_CAT || - opcode === OP_SUBSTR || - opcode === OP_LEFT || - opcode === OP_RIGHT || - opcode === OP_INVERT || - opcode === OP_AND || - opcode === OP_OR || - opcode === OP_XOR || - opcode === OP_2MUL || - opcode === OP_2DIV || - opcode === OP_MUL || - opcode === OP_DIV || - opcode === OP_MOD || - opcode === OP_LSHIFT || - opcode === OP_RSHIFT)) { - throw new Error("Encountered a disabled opcode"); - } - - if (exec && Buffer.isBuffer(opcode)) { - this.stack.push(opcode); - } else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF)) - switch (opcode) { - case OP_0: - this.stack.push(new Buffer([])); - break; - - case OP_1NEGATE: - case OP_1: - case OP_2: - case OP_3: - case OP_4: - case OP_5: - case OP_6: - case OP_7: - case OP_8: - case OP_9: - case OP_10: - case OP_11: - case OP_12: - case OP_13: - case OP_14: - case OP_15: - case OP_16: - var opint = opcode - OP_1 + 1; - var opbuf = intToBufferSM(opint); - this.stack.push(opbuf); - break; - - case OP_NOP: - case OP_NOP1: - case OP_NOP2: - case OP_NOP3: - case OP_NOP4: - case OP_NOP5: - case OP_NOP6: - case OP_NOP7: - case OP_NOP8: - case OP_NOP9: - case OP_NOP10: - break; - - case OP_IF: - case OP_NOTIF: - // if [statements] [else [statements]] endif - var value = false; - if (exec) { - value = castBool(this.stackPop()); - if (opcode === OP_NOTIF) { - value = !value; - } - } - execStack.push(value); - break; - - case OP_ELSE: - if (execStack.length < 1) { - throw new Error("Unmatched OP_ELSE"); - } - execStack[execStack.length - 1] = !execStack[execStack.length - 1]; - break; - - case OP_ENDIF: - if (execStack.length < 1) { - throw new Error("Unmatched OP_ENDIF"); - } - execStack.pop(); - break; - - case OP_VERIFY: - var value = castBool(this.stackTop()); - if (value) { - this.stackPop(); - } else { - throw new Error("OP_VERIFY negative"); - } - break; - - case OP_RETURN: - throw new Error("OP_RETURN"); + if (opcode.length > 520) { + throw new Error("Max push value size exceeded (>520)"); + } - case OP_TOALTSTACK: - altStack.push(this.stackPop()); - break; + if (opcode > OP_16 && ++opCount > 201) { + throw new Error("Opcode limit exceeded (>200)"); + } - case OP_FROMALTSTACK: - if (altStack.length < 1) { - throw new Error("OP_FROMALTSTACK with alt stack empty"); - } - this.stack.push(altStack.pop()); - break; + if (this.disableUnsafeOpcodes && + "number" === typeof opcode && + (opcode === OP_CAT || + opcode === OP_SUBSTR || + opcode === OP_LEFT || + opcode === OP_RIGHT || + opcode === OP_INVERT || + opcode === OP_AND || + opcode === OP_OR || + opcode === OP_XOR || + opcode === OP_2MUL || + opcode === OP_2DIV || + opcode === OP_MUL || + opcode === OP_DIV || + opcode === OP_MOD || + opcode === OP_LSHIFT || + opcode === OP_RSHIFT)) { + throw new Error("Encountered a disabled opcode"); + } - case OP_2DROP: - // (x1 x2 -- ) - this.stackPop(); - this.stackPop(); - break; - - case OP_2DUP: - // (x1 x2 -- x1 x2 x1 x2) - var v1 = this.stackTop(2); - var v2 = this.stackTop(1); - this.stack.push(v1); - this.stack.push(v2); - break; - - case OP_3DUP: - // (x1 x2 -- x1 x2 x1 x2) - var v1 = this.stackTop(3); - var v2 = this.stackTop(2); - var v3 = this.stackTop(1); - this.stack.push(v1); - this.stack.push(v2); - this.stack.push(v3); - break; - - case OP_2OVER: - // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) - var v1 = this.stackTop(4); - var v2 = this.stackTop(3); - this.stack.push(v1); - this.stack.push(v2); - break; - - case OP_2ROT: - // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) - var v1 = this.stackTop(6); - var v2 = this.stackTop(5); - this.stack.splice(this.stack.length - 6, 2); - this.stack.push(v1); - this.stack.push(v2); - break; - - case OP_2SWAP: - // (x1 x2 x3 x4 -- x3 x4 x1 x2) - this.stackSwap(4, 2); - this.stackSwap(3, 1); - break; - - case OP_IFDUP: - // (x - 0 | x x) - var value = this.stackTop(); - if (castBool(value)) { - this.stack.push(value); + if (exec && Buffer.isBuffer(opcode)) { + this.stack.push(opcode); + } else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF)) + switch (opcode) { + case OP_0: + this.stack.push(new Buffer([])); + break; + + case OP_1NEGATE: + case OP_1: + case OP_2: + case OP_3: + case OP_4: + case OP_5: + case OP_6: + case OP_7: + case OP_8: + case OP_9: + case OP_10: + case OP_11: + case OP_12: + case OP_13: + case OP_14: + case OP_15: + case OP_16: + var opint = opcode - OP_1 + 1; + var opbuf = intToBufferSM(opint); + this.stack.push(opbuf); + break; + + case OP_NOP: + case OP_NOP1: + case OP_NOP2: + case OP_NOP3: + case OP_NOP4: + case OP_NOP5: + case OP_NOP6: + case OP_NOP7: + case OP_NOP8: + case OP_NOP9: + case OP_NOP10: + break; + + case OP_IF: + case OP_NOTIF: + // if [statements] [else [statements]] endif + var value = false; + if (exec) { + value = castBool(this.stackPop()); + if (opcode === OP_NOTIF) { + value = !value; } - break; - - case OP_DEPTH: - // -- stacksize - var value = bignum(this.stack.length); - this.stack.push(intToBufferSM(value)); - break; - - case OP_DROP: - // (x -- ) + } + execStack.push(value); + break; + + case OP_ELSE: + if (execStack.length < 1) { + throw new Error("Unmatched OP_ELSE"); + } + execStack[execStack.length - 1] = !execStack[execStack.length - 1]; + break; + + case OP_ENDIF: + if (execStack.length < 1) { + throw new Error("Unmatched OP_ENDIF"); + } + execStack.pop(); + break; + + case OP_VERIFY: + var value = castBool(this.stackTop()); + if (value) { this.stackPop(); - break; - - case OP_DUP: - // (x -- x x) - this.stack.push(this.stackTop()); - break; - - case OP_NIP: - // (x1 x2 -- x2) - if (this.stack.length < 2) { - throw new Error("OP_NIP insufficient stack size"); - } - this.stack.splice(this.stack.length - 2, 1); - break; - - case OP_OVER: - // (x1 x2 -- x1 x2 x1) - this.stack.push(this.stackTop(2)); - break; - - case OP_PICK: - case OP_ROLL: - // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) - // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) - var n = castInt(this.stackPop()); - if (n < 0 || n >= this.stack.length) { - throw new Error("OP_PICK/OP_ROLL insufficient stack size"); - } - var value = this.stackTop(n + 1); - if (opcode === OP_ROLL) { - this.stack.splice(this.stack.length - n - 1, 1); - } + } else { + throw new Error("OP_VERIFY negative"); + } + break; + + case OP_RETURN: + throw new Error("OP_RETURN"); + + case OP_TOALTSTACK: + altStack.push(this.stackPop()); + break; + + case OP_FROMALTSTACK: + if (altStack.length < 1) { + throw new Error("OP_FROMALTSTACK with alt stack empty"); + } + this.stack.push(altStack.pop()); + break; + + case OP_2DROP: + // (x1 x2 -- ) + this.stackPop(); + this.stackPop(); + break; + + case OP_2DUP: + // (x1 x2 -- x1 x2 x1 x2) + var v1 = this.stackTop(2); + var v2 = this.stackTop(1); + this.stack.push(v1); + this.stack.push(v2); + break; + + case OP_3DUP: + // (x1 x2 -- x1 x2 x1 x2) + var v1 = this.stackTop(3); + var v2 = this.stackTop(2); + var v3 = this.stackTop(1); + this.stack.push(v1); + this.stack.push(v2); + this.stack.push(v3); + break; + + case OP_2OVER: + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) + var v1 = this.stackTop(4); + var v2 = this.stackTop(3); + this.stack.push(v1); + this.stack.push(v2); + break; + + case OP_2ROT: + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) + var v1 = this.stackTop(6); + var v2 = this.stackTop(5); + this.stack.splice(this.stack.length - 6, 2); + this.stack.push(v1); + this.stack.push(v2); + break; + + case OP_2SWAP: + // (x1 x2 x3 x4 -- x3 x4 x1 x2) + this.stackSwap(4, 2); + this.stackSwap(3, 1); + break; + + case OP_IFDUP: + // (x - 0 | x x) + var value = this.stackTop(); + if (castBool(value)) { this.stack.push(value); - break; - - case OP_ROT: - // (x1 x2 x3 -- x2 x3 x1) - // x2 x1 x3 after first swap - // x2 x3 x1 after second swap - this.stackSwap(3, 2); - this.stackSwap(2, 1); - break; - - case OP_SWAP: - // (x1 x2 -- x2 x1) - this.stackSwap(2, 1); - break; - - case OP_TUCK: - // (x1 x2 -- x2 x1 x2) - if (this.stack.length < 2) { - throw new Error("OP_TUCK insufficient stack size"); + } + break; + + case OP_DEPTH: + // -- stacksize + var value = bignum(this.stack.length); + this.stack.push(intToBufferSM(value)); + break; + + case OP_DROP: + // (x -- ) + this.stackPop(); + break; + + case OP_DUP: + // (x -- x x) + this.stack.push(this.stackTop()); + break; + + case OP_NIP: + // (x1 x2 -- x2) + if (this.stack.length < 2) { + throw new Error("OP_NIP insufficient stack size"); + } + this.stack.splice(this.stack.length - 2, 1); + break; + + case OP_OVER: + // (x1 x2 -- x1 x2 x1) + this.stack.push(this.stackTop(2)); + break; + + case OP_PICK: + case OP_ROLL: + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + var n = castInt(this.stackPop()); + if (n < 0 || n >= this.stack.length) { + throw new Error("OP_PICK/OP_ROLL insufficient stack size"); + } + var value = this.stackTop(n + 1); + if (opcode === OP_ROLL) { + this.stack.splice(this.stack.length - n - 1, 1); + } + this.stack.push(value); + break; + + case OP_ROT: + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + this.stackSwap(3, 2); + this.stackSwap(2, 1); + break; + + case OP_SWAP: + // (x1 x2 -- x2 x1) + this.stackSwap(2, 1); + break; + + case OP_TUCK: + // (x1 x2 -- x2 x1 x2) + if (this.stack.length < 2) { + throw new Error("OP_TUCK insufficient stack size"); + } + this.stack.splice(this.stack.length - 2, 0, this.stackTop()); + break; + + case OP_CAT: + // (x1 x2 -- out) + var v1 = this.stackTop(2); + var v2 = this.stackTop(1); + this.stackPop(); + this.stackPop(); + this.stack.push(Buffer.concat([v1, v2])); + break; + + case OP_SUBSTR: + // (in begin size -- out) + var buf = this.stackTop(3); + var start = castInt(this.stackTop(2)); + var len = castInt(this.stackTop(1)); + if (start < 0 || len < 0) { + throw new Error("OP_SUBSTR start < 0 or len < 0"); + } + if ((start + len) >= buf.length) { + throw new Error("OP_SUBSTR range out of bounds"); + } + this.stackPop(); + this.stackPop(); + this.stack[this.stack.length - 1] = buf.slice(start, start + len); + break; + + case OP_LEFT: + case OP_RIGHT: + // (in size -- out) + var buf = this.stackTop(2); + var size = castInt(this.stackTop(1)); + if (size < 0) { + throw new Error("OP_LEFT/OP_RIGHT size < 0"); + } + if (size > buf.length) { + size = buf.length; + } + this.stackPop(); + if (opcode === OP_LEFT) { + this.stack[this.stack.length - 1] = buf.slice(0, size); + } else { + this.stack[this.stack.length - 1] = buf.slice(buf.length - size); + } + break; + + case OP_SIZE: + // (in -- in size) + var value = bignum(this.stackTop().length); + this.stack.push(intToBufferSM(value)); + break; + + case OP_INVERT: + // (in - out) + var buf = this.stackTop(); + for (var i = 0, l = buf.length; i < l; i++) { + buf[i] = ~buf[i]; + } + break; + + case OP_AND: + case OP_OR: + case OP_XOR: + // (x1 x2 - out) + var v1 = this.stackTop(2); + var v2 = this.stackTop(1); + this.stackPop(); + this.stackPop(); + var out = new Buffer(Math.max(v1.length, v2.length)); + if (opcode === OP_AND) { + for (var i = 0, l = out.length; i < l; i++) { + out[i] = v1[i] & v2[i]; } - this.stack.splice(this.stack.length - 2, 0, this.stackTop()); - break; - - case OP_CAT: - // (x1 x2 -- out) - var v1 = this.stackTop(2); - var v2 = this.stackTop(1); - this.stackPop(); - this.stackPop(); - this.stack.push(Buffer.concat([v1, v2])); - break; - - case OP_SUBSTR: - // (in begin size -- out) - var buf = this.stackTop(3); - var start = castInt(this.stackTop(2)); - var len = castInt(this.stackTop(1)); - if (start < 0 || len < 0) { - throw new Error("OP_SUBSTR start < 0 or len < 0"); - } - if ((start + len) >= buf.length) { - throw new Error("OP_SUBSTR range out of bounds"); - } - this.stackPop(); - this.stackPop(); - this.stack[this.stack.length - 1] = buf.slice(start, start + len); - break; - - case OP_LEFT: - case OP_RIGHT: - // (in size -- out) - var buf = this.stackTop(2); - var size = castInt(this.stackTop(1)); - if (size < 0) { - throw new Error("OP_LEFT/OP_RIGHT size < 0"); + } else if (opcode === OP_OR) { + for (var i = 0, l = out.length; i < l; i++) { + out[i] = v1[i] | v2[i]; } - if (size > buf.length) { - size = buf.length; + } else if (opcode === OP_XOR) { + for (var i = 0, l = out.length; i < l; i++) { + out[i] = v1[i] ^ v2[i]; } - this.stackPop(); - if (opcode === OP_LEFT) { - this.stack[this.stack.length - 1] = buf.slice(0, size); + } + this.stack.push(out); + break; + + case OP_EQUAL: + case OP_EQUALVERIFY: + //case OP_NOTEQUAL: // use OP_NUMNOTEQUAL + // (x1 x2 - bool) + var v1 = this.stackTop(2); + var v2 = this.stackTop(1); + + var value = buffertools.compare(v1, v2) === 0; + + // OP_NOTEQUAL is disabled because it would be too easy to say + // something like n != 1 and have some wiseguy pass in 1 with extra + // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) + //if (opcode == OP_NOTEQUAL) + // fEqual = !fEqual; + + this.stackPop(); + this.stackPop(); + this.stack.push(new Buffer([value ? 1 : 0])); + if (opcode === OP_EQUALVERIFY) { + if (value) { + this.stackPop(); } else { - this.stack[this.stack.length - 1] = buf.slice(buf.length - size); + throw new Error("OP_EQUALVERIFY negative"); } - break; - - case OP_SIZE: - // (in -- in size) - var value = bignum(this.stackTop().length); - this.stack.push(intToBufferSM(value)); - break; - - case OP_INVERT: - // (in - out) - var buf = this.stackTop(); - for (var i = 0, l = buf.length; i < l; i++) { - buf[i] = ~buf[i]; - } - break; - - case OP_AND: - case OP_OR: - case OP_XOR: - // (x1 x2 - out) - var v1 = this.stackTop(2); - var v2 = this.stackTop(1); - this.stackPop(); - this.stackPop(); - var out = new Buffer(Math.max(v1.length, v2.length)); - if (opcode === OP_AND) { - for (var i = 0, l = out.length; i < l; i++) { - out[i] = v1[i] & v2[i]; + } + break; + + case OP_1ADD: + case OP_1SUB: + case OP_2MUL: + case OP_2DIV: + case OP_NEGATE: + case OP_ABS: + case OP_NOT: + case OP_0NOTEQUAL: + // (in -- out) + var num = bufferSMToInt(this.stackTop()); + switch (opcode) { + case OP_1ADD: + num = num.add(bignum(1)); + break; + case OP_1SUB: + num = num.sub(bignum(1)); + break; + case OP_2MUL: + num = num.mul(bignum(2)); + break; + case OP_2DIV: + num = num.div(bignum(2)); + break; + case OP_NEGATE: + num = num.neg(); + break; + case OP_ABS: + num = num.abs(); + break; + case OP_NOT: + num = bignum(num.cmp(0) == 0 ? 1 : 0); + break; + case OP_0NOTEQUAL: + num = bignum(num.cmp(0) == 0 ? 0 : 1); + break; + } + this.stack[this.stack.length - 1] = intToBufferSM(num); + break; + + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_MOD: + case OP_LSHIFT: + case OP_RSHIFT: + case OP_BOOLAND: + case OP_BOOLOR: + case OP_NUMEQUAL: + case OP_NUMEQUALVERIFY: + case OP_NUMNOTEQUAL: + case OP_LESSTHAN: + case OP_GREATERTHAN: + case OP_LESSTHANOREQUAL: + case OP_GREATERTHANOREQUAL: + case OP_MIN: + case OP_MAX: + // (x1 x2 -- out) + var v1 = bufferSMToInt(this.stackTop(2)); + var v2 = bufferSMToInt(this.stackTop(1)); + var num; + switch (opcode) { + case OP_ADD: + num = v1.add(v2); + break; + case OP_SUB: + num = v1.sub(v2); + break; + case OP_MUL: + num = v1.mul(v2); + break; + case OP_DIV: + num = v1.div(v2); + break; + case OP_MOD: + num = v1.mod(v2); + break; + + case OP_LSHIFT: + if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) { + throw new Error("OP_LSHIFT parameter out of bounds"); } - } else if (opcode === OP_OR) { - for (var i = 0, l = out.length; i < l; i++) { - out[i] = v1[i] | v2[i]; - } - } else if (opcode === OP_XOR) { - for (var i = 0, l = out.length; i < l; i++) { - out[i] = v1[i] ^ v2[i]; - } - } - this.stack.push(out); - break; - - case OP_EQUAL: - case OP_EQUALVERIFY: - //case OP_NOTEQUAL: // use OP_NUMNOTEQUAL - // (x1 x2 - bool) - var v1 = this.stackTop(2); - var v2 = this.stackTop(1); - - var value = buffertools.compare(v1, v2) === 0; - - // OP_NOTEQUAL is disabled because it would be too easy to say - // something like n != 1 and have some wiseguy pass in 1 with extra - // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) - //if (opcode == OP_NOTEQUAL) - // fEqual = !fEqual; + num = v1.shiftLeft(v2); + break; - this.stackPop(); - this.stackPop(); - this.stack.push(new Buffer([value ? 1 : 0])); - console.log(script.toHumanReadable()); - if (opcode === OP_EQUALVERIFY) { - if (value) { - this.stackPop(); - } else { - console.log(v1); - console.log(v2); - throw new Error("OP_EQUALVERIFY negative"); + case OP_RSHIFT: + if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) { + throw new Error("OP_RSHIFT parameter out of bounds"); } + num = v1.shiftRight(v2); + break; + + case OP_BOOLAND: + num = bignum((v1.cmp(0) != 0 && v2.cmp(0) != 0) ? 1 : 0); + break; + + case OP_BOOLOR: + num = bignum((v1.cmp(0) != 0 || v2.cmp(0) != 0) ? 1 : 0); + break; + + case OP_NUMEQUAL: + case OP_NUMEQUALVERIFY: + num = bignum(v1.cmp(v2) == 0 ? 1 : 0); + break; + + case OP_NUMNOTEQUAL: + ; + num = bignum(v1.cmp(v2) != 0 ? 1 : 0); + break; + + case OP_LESSTHAN: + num = bignum(v1.lt(v2) ? 1 : 0); + break; + + case OP_GREATERTHAN: + num = bignum(v1.gt(v2) ? 1 : 0); + break; + + case OP_LESSTHANOREQUAL: + num = bignum(v1.gt(v2) ? 0 : 1); + break; + + case OP_GREATERTHANOREQUAL: + num = bignum(v1.lt(v2) ? 0 : 1); + break; + + case OP_MIN: + num = (v1.lt(v2) ? v1 : v2); + break; + case OP_MAX: + num = (v1.gt(v2) ? v1 : v2); + break; + } + this.stackPop(); + this.stackPop(); + this.stack.push(intToBufferSM(num)); + + if (opcode === OP_NUMEQUALVERIFY) { + if (castBool(this.stackTop())) { + this.stackPop(); + } else { + throw new Error("OP_NUMEQUALVERIFY negative"); } - break; - - case OP_1ADD: - case OP_1SUB: - case OP_2MUL: - case OP_2DIV: - case OP_NEGATE: - case OP_ABS: - case OP_NOT: - case OP_0NOTEQUAL: - // (in -- out) - var num = bufferSMToInt(this.stackTop()); - switch (opcode) { - case OP_1ADD: - num = num.add(bignum(1)); - break; - case OP_1SUB: - num = num.sub(bignum(1)); - break; - case OP_2MUL: - num = num.mul(bignum(2)); - break; - case OP_2DIV: - num = num.div(bignum(2)); - break; - case OP_NEGATE: - num = num.neg(); - break; - case OP_ABS: - num = num.abs(); - break; - case OP_NOT: - num = bignum(num.cmp(0) == 0 ? 1 : 0); - break; - case OP_0NOTEQUAL: - num = bignum(num.cmp(0) == 0 ? 0 : 1); - break; + } + break; + + case OP_WITHIN: + // (x min max -- out) + var v1 = bufferSMToInt(this.stackTop(3)); + var v2 = bufferSMToInt(this.stackTop(2)); + var v3 = bufferSMToInt(this.stackTop(1)); + this.stackPop(); + this.stackPop(); + this.stackPop(); + var value = v1.cmp(v2) >= 0 && v1.cmp(v3) < 0; + this.stack.push(intToBufferSM(value ? 1 : 0)); + break; + + case OP_RIPEMD160: + case OP_SHA1: + case OP_SHA256: + case OP_HASH160: + case OP_HASH256: + // (in -- hash) + var value = this.stackPop(); + var hash; + if (opcode === OP_RIPEMD160) { + hash = Util.ripe160(value); + } else if (opcode === OP_SHA1) { + hash = Util.sha1(value); + } else if (opcode === OP_SHA256) { + hash = Util.sha256(value); + } else if (opcode === OP_HASH160) { + hash = Util.sha256ripe160(value); + } else if (opcode === OP_HASH256) { + hash = Util.twoSha256(value); + } + this.stack.push(hash); + break; + + case OP_CODESEPARATOR: + // Hash starts after the code separator + hashStart = pc; + break; + + case OP_CHECKSIG: + case OP_CHECKSIGVERIFY: + // (sig pubkey -- bool) + var sig = this.stackTop(2); + var pubkey = this.stackTop(1); + + // Get the part of this script since the last OP_CODESEPARATOR + var scriptChunks = script.chunks.slice(hashStart); + + // Convert to binary + var scriptCode = Script.fromChunks(scriptChunks); + + // Remove signature if present (a signature can't sign itself) + scriptCode.findAndDelete(sig); + + // + this.isCanonicalSignature(new Buffer(sig)); + + // Verify signature + checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) { + var success; + + if (e) { + // We intentionally ignore errors during signature verification and + // treat these cases as an invalid signature. + success = false; + } else { + success = result; } - this.stack[this.stack.length - 1] = intToBufferSM(num); - break; - - case OP_ADD: - case OP_SUB: - case OP_MUL: - case OP_DIV: - case OP_MOD: - case OP_LSHIFT: - case OP_RSHIFT: - case OP_BOOLAND: - case OP_BOOLOR: - case OP_NUMEQUAL: - case OP_NUMEQUALVERIFY: - case OP_NUMNOTEQUAL: - case OP_LESSTHAN: - case OP_GREATERTHAN: - case OP_LESSTHANOREQUAL: - case OP_GREATERTHANOREQUAL: - case OP_MIN: - case OP_MAX: - // (x1 x2 -- out) - var v1 = bufferSMToInt(this.stackTop(2)); - var v2 = bufferSMToInt(this.stackTop(1)); - var num; - switch (opcode) { - case OP_ADD: - num = v1.add(v2); - break; - case OP_SUB: - num = v1.sub(v2); - break; - case OP_MUL: - num = v1.mul(v2); - break; - case OP_DIV: - num = v1.div(v2); - break; - case OP_MOD: - num = v1.mod(v2); - break; - - case OP_LSHIFT: - if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) { - throw new Error("OP_LSHIFT parameter out of bounds"); - } - num = v1.shiftLeft(v2); - break; - case OP_RSHIFT: - if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) { - throw new Error("OP_RSHIFT parameter out of bounds"); - } - num = v1.shiftRight(v2); - break; - - case OP_BOOLAND: - num = bignum((v1.cmp(0) != 0 && v2.cmp(0) != 0) ? 1 : 0); - break; - - case OP_BOOLOR: - num = bignum((v1.cmp(0) != 0 || v2.cmp(0) != 0) ? 1 : 0); - break; - - case OP_NUMEQUAL: - case OP_NUMEQUALVERIFY: - num = bignum(v1.cmp(v2) == 0 ? 1 : 0); - break; - - case OP_NUMNOTEQUAL: - ; - num = bignum(v1.cmp(v2) != 0 ? 1 : 0); - break; - - case OP_LESSTHAN: - num = bignum(v1.lt(v2) ? 1 : 0); - break; - - case OP_GREATERTHAN: - num = bignum(v1.gt(v2) ? 1 : 0); - break; - - case OP_LESSTHANOREQUAL: - num = bignum(v1.gt(v2) ? 0 : 1); - break; - - case OP_GREATERTHANOREQUAL: - num = bignum(v1.lt(v2) ? 0 : 1); - break; - - case OP_MIN: - num = (v1.lt(v2) ? v1 : v2); - break; - case OP_MAX: - num = (v1.gt(v2) ? v1 : v2); - break; - } + // Update stack this.stackPop(); this.stackPop(); - this.stack.push(intToBufferSM(num)); - - if (opcode === OP_NUMEQUALVERIFY) { - if (castBool(this.stackTop())) { + this.stack.push(new Buffer([success ? 1 : 0])); + if (opcode === OP_CHECKSIGVERIFY) { + if (success) { this.stackPop(); } else { - throw new Error("OP_NUMEQUALVERIFY negative"); + throw new Error("OP_CHECKSIGVERIFY negative"); } } - break; - case OP_WITHIN: - // (x min max -- out) - var v1 = bufferSMToInt(this.stackTop(3)); - var v2 = bufferSMToInt(this.stackTop(2)); - var v3 = bufferSMToInt(this.stackTop(1)); - this.stackPop(); - this.stackPop(); - this.stackPop(); - var value = v1.cmp(v2) >= 0 && v1.cmp(v3) < 0; - this.stack.push(intToBufferSM(value ? 1 : 0)); - break; - - case OP_RIPEMD160: - case OP_SHA1: - case OP_SHA256: - case OP_HASH160: - case OP_HASH256: - // (in -- hash) - var value = this.stackPop(); - var hash; - if (opcode === OP_RIPEMD160) { - hash = Util.ripe160(value); - } else if (opcode === OP_SHA1) { - hash = Util.sha1(value); - } else if (opcode === OP_SHA256) { - hash = Util.sha256(value); - } else if (opcode === OP_HASH160) { - hash = Util.sha256ripe160(value); - } else if (opcode === OP_HASH256) { - hash = Util.twoSha256(value); - } - this.stack.push(hash); - break; - - case OP_CODESEPARATOR: - // Hash starts after the code separator - hashStart = pc; - break; - - case OP_CHECKSIG: - case OP_CHECKSIGVERIFY: - // (sig pubkey -- bool) - var sig = this.stackTop(2); - var pubkey = this.stackTop(1); - - // Get the part of this script since the last OP_CODESEPARATOR - var scriptChunks = script.chunks.slice(hashStart); - - // Convert to binary - var scriptCode = Script.fromChunks(scriptChunks); - - // Remove signature if present (a signature can't sign itself) + // Run next step + executeStep.call(this, cb); + }.bind(this)); + + // Note that for asynchronous opcodes we have to return here to prevent + // the next opcode from being executed. + return; + + case OP_CHECKMULTISIG: + case OP_CHECKMULTISIGVERIFY: + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + var keysCount = castInt(this.stackPop()); + if (keysCount < 0 || keysCount > 20) { + throw new Error("OP_CHECKMULTISIG keysCount out of bounds"); + } + opCount += keysCount; + if (opCount > 201) { + throw new Error("Opcode limit exceeded (>200)"); + } + var keys = []; + for (var i = 0, l = keysCount; i < l; i++) { + keys.push(this.stackPop()); + } + var sigsCount = castInt(this.stackPop()); + if (sigsCount < 0 || sigsCount > keysCount) { + throw new Error("OP_CHECKMULTISIG sigsCount out of bounds"); + } + var sigs = []; + for (var i = 0, l = sigsCount; i < l; i++) { + sigs.push(this.stackPop()); + } + + // The original client has a bug where it pops an extra element off the + // stack. It can't be fixed without causing a chain split and we need to + // imitate this behavior as well. + this.stackPop(); + + // Get the part of this script since the last OP_CODESEPARATOR + var scriptChunks = script.chunks.slice(hashStart); + + // Convert to binary + var scriptCode = Script.fromChunks(scriptChunks); + + // Drop the signatures, since a signature can't sign itself + var that = this; + sigs.forEach(function(sig) { + that.isCanonicalSignature(new Buffer(sig)); scriptCode.findAndDelete(sig); - - // - this.isCanonicalSignature(new Buffer(sig)); - - // Verify signature - checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) { - try { - var success; - - if (e) { - // We intentionally ignore errors during signature verification and - // treat these cases as an invalid signature. - success = false; + }); + + var success = true, + isig = 0, + ikey = 0; + checkMultiSigStep.call(this); + + function checkMultiSigStep() { + if (success && sigsCount > 0) { + var sig = sigs[isig]; + var key = keys[ikey]; + + checkSig(sig, key, scriptCode, tx, inIndex, hashType, function(e, result) { + if (!e && result) { + isig++; + sigsCount--; } else { - success = result; - } + ikey++; + keysCount--; - // Update stack - this.stackPop(); - this.stackPop(); - this.stack.push(new Buffer([success ? 1 : 0])); - if (opcode === OP_CHECKSIGVERIFY) { - if (success) { - this.stackPop(); - } else { - throw new Error("OP_CHECKSIGVERIFY negative"); + // If there are more signatures than keys left, then too many + // signatures have failed + if (sigsCount > keysCount) { + success = false; } } - // Run next step - executeStep.call(this, cb); - } catch (e) { - cb(e); - } - }.bind(this)); - - // Note that for asynchronous opcodes we have to return here to prevent - // the next opcode from being executed. - return; - - case OP_CHECKMULTISIG: - case OP_CHECKMULTISIGVERIFY: - // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) - var keysCount = castInt(this.stackPop()); - if (keysCount < 0 || keysCount > 20) { - throw new Error("OP_CHECKMULTISIG keysCount out of bounds"); - } - opCount += keysCount; - if (opCount > 201) { - throw new Error("Opcode limit exceeded (>200)"); - } - var keys = []; - for (var i = 0, l = keysCount; i < l; i++) { - keys.push(this.stackPop()); - } - var sigsCount = castInt(this.stackPop()); - if (sigsCount < 0 || sigsCount > keysCount) { - throw new Error("OP_CHECKMULTISIG sigsCount out of bounds"); - } - var sigs = []; - for (var i = 0, l = sigsCount; i < l; i++) { - sigs.push(this.stackPop()); - } - - // The original client has a bug where it pops an extra element off the - // stack. It can't be fixed without causing a chain split and we need to - // imitate this behavior as well. - this.stackPop(); - - // Get the part of this script since the last OP_CODESEPARATOR - var scriptChunks = script.chunks.slice(hashStart); - - // Convert to binary - var scriptCode = Script.fromChunks(scriptChunks); - - // Drop the signatures, since a signature can't sign itself - var that = this; - sigs.forEach(function(sig) { - that.isCanonicalSignature(new Buffer(sig)); - scriptCode.findAndDelete(sig); - }); - - var success = true, - isig = 0, - ikey = 0; - checkMultiSigStep.call(this); - - function checkMultiSigStep() { - try { - if (success && sigsCount > 0) { - var sig = sigs[isig]; - var key = keys[ikey]; - - checkSig(sig, key, scriptCode, tx, inIndex, hashType, function(e, result) { - try { - if (!e && result) { - isig++; - sigsCount--; - } else { - ikey++; - keysCount--; - - // If there are more signatures than keys left, then too many - // signatures have failed - if (sigsCount > keysCount) { - success = false; - } - } - - checkMultiSigStep.call(this); - } catch (e) { - cb(e); - } - }.bind(this)); + checkMultiSigStep.call(this); + }.bind(this)); + } else { + this.stack.push(new Buffer([success ? 1 : 0])); + if (opcode === OP_CHECKMULTISIGVERIFY) { + if (success) { + this.stackPop(); } else { - this.stack.push(new Buffer([success ? 1 : 0])); - if (opcode === OP_CHECKMULTISIGVERIFY) { - if (success) { - this.stackPop(); - } else { - throw new Error("OP_CHECKMULTISIGVERIFY negative"); - } - } - - // Run next step - executeStep.call(this, cb); + throw new Error("OP_CHECKMULTISIGVERIFY negative"); } - } catch (e) { - cb(e); } - }; - // Note that for asynchronous opcodes we have to return here to prevent - // the next opcode from being executed. - return; + // Run next step + executeStep.call(this, cb); + } + }; - default: - throw new Error("Unknown opcode encountered"); - } + // Note that for asynchronous opcodes we have to return here to prevent + // the next opcode from being executed. + return; - // Size limits - if ((this.stack.length + altStack.length) > 1000) { - throw new Error("Maximum stack size exceeded"); + default: + throw new Error("Unknown opcode encountered"); } - // Run next step - if (pc % 100) { - // V8 allows for much deeper stacks than Bitcoin's scripting language, - // but just to be safe, we'll reset the stack every 100 steps - process.nextTick(executeStep.bind(this, cb)); - } else { - executeStep.call(this, cb); - } - } catch (e) { - log.debug("Script aborted: " + - (e.message ? e.message : e)); - cb(e); + // Size limits + if ((this.stack.length + altStack.length) > 1000) { + throw new Error("Maximum stack size exceeded"); + } + + // Run next step + if (false && pc % 100) { + // V8 allows for much deeper stacks than Bitcoin's scripting language, + // but just to be safe, we'll reset the stack every 100 steps + process.nextTick(executeStep.bind(this, cb)); + } else { + executeStep.call(this, cb); } } }; @@ -849,15 +829,15 @@ ScriptInterpreter.prototype.stackSwap = function stackSwap(a, b) { * integer. Any longer Buffer is converted to a hex string. */ ScriptInterpreter.prototype.getPrimitiveStack = function getPrimitiveStack() { - return this.stack.map(function(entry) { - if (entry.length > 2) { - return buffertools.toHex(entry.slice(0)); + return this.stack.map(function(chunk) { + if (chunk.length > 2) { + return buffertools.toHex(chunk.slice(0)); } - var num = bufferSMToInt(entry); + var num = bufferSMToInt(chunk); if (num.cmp(-128) >= 0 && num.cmp(127) <= 0) { return num.toNumber(); } else { - return buffertools.toHex(entry.slice(0)); + return buffertools.toHex(chunk.slice(0)); } }); }; @@ -904,12 +884,7 @@ ScriptInterpreter.verify = } // Cast result to bool - try { - var result = si.getResult(); - } catch (err) { - callback(err); - return; - } + var result = si.getResult(); callback(null, result); }); @@ -992,8 +967,15 @@ ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey, var that = this; this.eval(scriptSig, txTo, nIn, hashType, function(err) { if (err) callback(err); - else that.verifyStep2(scriptSig, scriptPubKey, txTo, nIn, - hashType, callback, siCopy); + else { + var e = new Error('dummy'); + var stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '') + .replace(/^\s+at\s+/gm, '') + .replace(/^Object.\s*\(/gm, '{anonymous}()@') + .split('\n'); + that.verifyStep2(scriptSig, scriptPubKey, txTo, nIn, + hashType, callback, siCopy); + } }); }; @@ -1013,17 +995,13 @@ var checkSig = ScriptInterpreter.checkSig = } sig = sig.slice(0, sig.length - 1); - try { - // Signature verification requires a special hash procedure - var hash = tx.hashForSignature(scriptCode, n, hashType); + // Signature verification requires a special hash procedure + var hash = tx.hashForSignature(scriptCode, n, hashType); - // Verify signature - var key = new Util.BitcoinKey(); - key.public = pubkey; - key.verifySignature(hash, sig, callback); - } catch (err) { - callback(null, false); - } + // Verify signature + var key = new Key(); + key.public = pubkey; + key.verifySignature(hash, sig, callback); }; ScriptInterpreter.prototype.isCanonicalSignature = function(sig) { diff --git a/Transaction.js b/Transaction.js index 25df323..f6a5ca3 100644 --- a/Transaction.js +++ b/Transaction.js @@ -1,22 +1,22 @@ -var imports = require('soop').imports(); -var config = imports.config || require('./config'); -var log = imports.log || require('./util/log'); -var Address = imports.Address || require('./Address'); -var Script = imports.Script || require('./Script'); -var ScriptInterpreter = imports.ScriptInterpreter || require('./ScriptInterpreter'); -var util = imports.util || require('./util/util'); -var bignum = imports.bignum || require('bignum'); -var Put = imports.Put || require('bufferput'); -var Parser = imports.Parser || require('./util/BinaryParser'); -var Step = imports.Step || require('step'); -var buffertools = imports.buffertools || require('buffertools'); -var error = imports.error || require('./util/error'); -var networks = imports.networks || require('./networks'); -var WalletKey = imports.WalletKey || require('./WalletKey'); -var PrivateKey = imports.PrivateKey || require('./PrivateKey'); +var imports = require('soop').imports(); +var config = imports.config || require('./config'); +var log = imports.log || require('./util/log'); +var Address = imports.Address || require('./Address'); +var Script = imports.Script || require('./Script'); +var ScriptInterpreter = imports.ScriptInterpreter || require('./ScriptInterpreter'); +var util = imports.util || require('./util/util'); +var bignum = imports.bignum || require('bignum'); +var Put = imports.Put || require('bufferput'); +var Parser = imports.Parser || require('./util/BinaryParser'); +var Step = imports.Step || require('step'); +var buffertools = imports.buffertools || require('buffertools'); +var error = imports.error || require('./util/error'); +var networks = imports.networks || require('./networks'); +var WalletKey = imports.WalletKey || require('./WalletKey'); +var PrivateKey = imports.PrivateKey || require('./PrivateKey'); var COINBASE_OP = Buffer.concat([util.NULL_HASH, new Buffer('FFFFFFFF', 'hex')]); -var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); +var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); function TransactionIn(data) { if ("object" !== typeof data) { @@ -34,7 +34,7 @@ function TransactionIn(data) { } } this.s = Buffer.isBuffer(data.s) ? data.s : - Buffer.isBuffer(data.script) ? data.script : util.EMPTY_BUFFER; + Buffer.isBuffer(data.script) ? data.script : util.EMPTY_BUFFER; this.q = data.q ? data.q : data.sequence; } @@ -64,15 +64,15 @@ TransactionIn.prototype.getOutpointHash = function getOutpointHash() { }; TransactionIn.prototype.getOutpointIndex = function getOutpointIndex() { - return (this.o[32] ) + - (this.o[33] << 8) + - (this.o[34] << 16) + - (this.o[35] << 24); + return (this.o[32]) + + (this.o[33] << 8) + + (this.o[34] << 16) + + (this.o[35] << 24); }; TransactionIn.prototype.setOutpointIndex = function setOutpointIndex(n) { - this.o[32] = n & 0xff; - this.o[33] = n >> 8 & 0xff; + this.o[32] = n & 0xff; + this.o[33] = n >> 8 & 0xff; this.o[34] = n >> 16 & 0xff; this.o[35] = n >> 24 & 0xff; }; @@ -106,14 +106,14 @@ function Transaction(data) { this.hash = data.hash || null; this.version = data.version; this.lock_time = data.lock_time; - this.ins = Array.isArray(data.ins) ? data.ins.map(function (data) { + this.ins = Array.isArray(data.ins) ? data.ins.map(function(data) { var txin = new TransactionIn(); txin.s = data.s; txin.q = data.q; txin.o = data.o; return txin; }) : []; - this.outs = Array.isArray(data.outs) ? data.outs.map(function (data) { + this.outs = Array.isArray(data.outs) ? data.outs.map(function(data) { var txout = new TransactionOut(); txout.v = data.v; txout.s = data.s; @@ -125,7 +125,7 @@ this.class = Transaction; Transaction.In = TransactionIn; Transaction.Out = TransactionOut; -Transaction.prototype.isCoinBase = function () { +Transaction.prototype.isCoinBase = function() { return this.ins.length == 1 && this.ins[0].isCoinBase(); }; @@ -152,12 +152,12 @@ Transaction.prototype.serialize = function serialize() { bufs.push(buf); bufs.push(util.varIntBuf(this.ins.length)); - this.ins.forEach(function (txin) { + this.ins.forEach(function(txin) { bufs.push(txin.serialize()); }); bufs.push(util.varIntBuf(this.outs.length)); - this.outs.forEach(function (txout) { + this.outs.forEach(function(txout) { bufs.push(txout.serialize()); }); @@ -176,7 +176,7 @@ Transaction.prototype.getBuffer = function getBuffer() { }; Transaction.prototype.calcHash = function calcHash() { - this.hash = util.twoSha256(this.getBuffer()); + this.hash = util.twoSha256(this.getBuffer()); return this.hash; }; @@ -204,7 +204,7 @@ Transaction.prototype.inputs = function inputs() { } return res; -} +}; /** * Load and cache transaction inputs. @@ -218,11 +218,11 @@ Transaction.prototype.inputs = function inputs() { * @param {Function} callback Function to call on completion. */ Transaction.prototype.cacheInputs = -function cacheInputs(blockChain, txStore, wait, callback) { - var self = this; + function cacheInputs(blockChain, txStore, wait, callback) { + var self = this; - var txCache = new TransactionInputsCache(this); - txCache.buffer(blockChain, txStore, wait, callback); + var txCache = new TransactionInputsCache(this); + txCache.buffer(blockChain, txStore, wait, callback); }; Transaction.prototype.verify = function verify(txCache, blockChain, callback) { @@ -244,7 +244,7 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) { if (!fromTxOuts) { throw new MissingSourceError( "Source tx " + util.formatHash(outHash) + - " for inputs " + n + " not found", + " for inputs " + n + " not found", // We store the hash of the missing tx in the error // so that the txStore can watch out for it. outHash.toString('base64') @@ -254,8 +254,8 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) { var txout = fromTxOuts[outIndex]; if (!txout) { - throw new Error("Source output index "+outIndex+ - " for input "+n+" out of bounds"); + throw new Error("Source output index " + outIndex + + " for input " + n + " out of bounds"); } return txout; @@ -269,7 +269,7 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) { throw new Error("Coinbase tx are invalid unless part of a block"); } - self.ins.forEach(function (txin, n) { + self.ins.forEach(function(txin, n) { var txout = getTxOut(txin, n); // TODO: Verify coinbase maturity @@ -289,9 +289,9 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) { if (!results[i]) { var txout = getTxOut(self.ins[i]); log.debug('Script evaluated to false'); - log.debug('|- scriptSig', ""+self.ins[i].getScript()); - log.debug('`- scriptPubKey', ""+txout.getScript()); - throw new VerificationError('Script for input '+i+' evaluated to false'); + log.debug('|- scriptSig', "" + self.ins[i].getScript()); + log.debug('`- scriptPubKey', "" + txout.getScript()); + throw new VerificationError('Script for input ' + i + ' evaluated to false'); } } @@ -307,38 +307,35 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) { function checkConflicts(err, count) { if (err) throw err; - self.outs.forEach(function (txout) { + self.outs.forEach(function(txout) { valueOut = valueOut.add(util.valueToBigInt(txout.v)); }); if (valueIn.cmp(valueOut) < 0) { var outValue = util.formatValue(valueOut); var inValue = util.formatValue(valueIn); - throw new Error("Tx output value (BTC "+outValue+") "+ - "exceeds input value (BTC "+inValue+")"); + throw new Error("Tx output value (BTC " + outValue + ") " + + "exceeds input value (BTC " + inValue + ")"); } var fees = valueIn.sub(valueOut); if (count) { // Spent output detected, retrieve transaction that spends it - blockChain.getConflictingTransactions(outpoints, function (err, results) { + blockChain.getConflictingTransactions(outpoints, function(err, results) { if (results.length) { if (buffertools.compare(results[0].getHash(), self.getHash()) === 0) { - log.warn("Detected tx re-add (recoverable db corruption): " - + util.formatHashAlt(results[0].getHash())); + log.warn("Detected tx re-add (recoverable db corruption): " + util.formatHashAlt(results[0].getHash())); // TODO: Needs to return an error for the memory pool case? callback(null, fees); } else { - callback(new Error("At least one referenced output has" - + " already been spent in tx " - + util.formatHashAlt(results[0].getHash()))); + callback(new Error("At least one referenced output has" + " already been spent in tx " + util.formatHashAlt(results[0].getHash()))); } } else { - callback(new Error("Outputs of this transaction are spent, but "+ - "the transaction(s) that spend them are not "+ - "available. This probably means you need to "+ - "reset your database.")); + callback(new Error("Outputs of this transaction are spent, but " + + "the transaction(s) that spend them are not " + + "available. This probably means you need to " + + "reset your database.")); } }); return; @@ -352,13 +349,13 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) { }; Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) { - var valid = ScriptInterpreter.verifyFull( - this.ins[n].getScript(), + var scriptSig = this.ins[n].getScript(); + return ScriptInterpreter.verifyFull( + scriptSig, scriptPubKey, this, n, 0, opts, callback); - return valid; }; /** @@ -375,61 +372,47 @@ Transaction.prototype.getAffectedKeys = function getAffectedKeys(txCache) { // Index any pubkeys affected by the outputs of this transaction for (var i = 0, l = this.outs.length; i < l; i++) { - try { - var txout = this.outs[i]; - var script = txout.getScript(); + var txout = this.outs[i]; + var script = txout.getScript(); - var outPubKey = script.simpleOutPubKeyHash(); - if (outPubKey) { - this.affects.push(outPubKey); - } - } catch (err) { - // It's not our job to validate, so we just ignore any errors and issue - // a very low level log message. - log.debug("Unable to determine affected pubkeys: " + - (err.stack ? err.stack : ""+err)); + var outPubKey = script.simpleOutPubKeyHash(); + if (outPubKey) { + this.affects.push(outPubKey); } }; // Index any pubkeys affected by the inputs of this transaction var txIndex = txCache.txIndex; for (var i = 0, l = this.ins.length; i < l; i++) { - try { - var txin = this.ins[i]; + var txin = this.ins[i]; - if (txin.isCoinBase()) continue; + if (txin.isCoinBase()) continue; - // In the case of coinbase or IP transactions, the txin doesn't - // actually contain the pubkey, so we look at the referenced txout - // instead. - var outHash = txin.getOutpointHash(); - var outIndex = txin.getOutpointIndex(); - var outHashBase64 = outHash.toString('base64'); - var fromTxOuts = txIndex[outHashBase64]; + // In the case of coinbase or IP transactions, the txin doesn't + // actually contain the pubkey, so we look at the referenced txout + // instead. + var outHash = txin.getOutpointHash(); + var outIndex = txin.getOutpointIndex(); + var outHashBase64 = outHash.toString('base64'); + var fromTxOuts = txIndex[outHashBase64]; - if (!fromTxOuts) { - throw new Error("Input not found!"); - } + if (!fromTxOuts) { + throw new Error("Input not found!"); + } - var txout = fromTxOuts[outIndex]; - var script = txout.getScript(); + var txout = fromTxOuts[outIndex]; + var script = txout.getScript(); - var outPubKey = script.simpleOutPubKeyHash(); - if (outPubKey) { - this.affects.push(outPubKey); - } - } catch (err) { - // It's not our job to validate, so we just ignore any errors and issue - // a very low level log message. - log.debug("Unable to determine affected pubkeys: " + - (err.stack ? err.stack : ""+err)); + var outPubKey = script.simpleOutPubKeyHash(); + if (outPubKey) { + this.affects.push(outPubKey); } } } var affectedKeys = {}; - this.affects.forEach(function (pubKeyHash) { + this.affects.forEach(function(pubKeyHash) { affectedKeys[pubKeyHash.toString('base64')] = pubKeyHash; }); @@ -443,114 +426,114 @@ var SIGHASH_NONE = 2; var SIGHASH_SINGLE = 3; var SIGHASH_ANYONECANPAY = 80; -Transaction.SIGHASH_ALL=SIGHASH_ALL; -Transaction.SIGHASH_NONE=SIGHASH_NONE; -Transaction.SIGHASH_SINGLE=SIGHASH_SINGLE; -Transaction.SIGHASH_ANYONECANPAY=SIGHASH_ANYONECANPAY; +Transaction.SIGHASH_ALL = SIGHASH_ALL; +Transaction.SIGHASH_NONE = SIGHASH_NONE; +Transaction.SIGHASH_SINGLE = SIGHASH_SINGLE; +Transaction.SIGHASH_ANYONECANPAY = SIGHASH_ANYONECANPAY; Transaction.prototype.hashForSignature = -function hashForSignature(script, inIndex, hashType) { - if (+inIndex !== inIndex || + function hashForSignature(script, inIndex, hashType) { + if (+inIndex !== inIndex || inIndex < 0 || inIndex >= this.ins.length) { - throw new Error("Input index '"+inIndex+"' invalid or out of bounds "+ - "("+this.ins.length+" inputs)"); - } - - // Clone transaction - var txTmp = new Transaction(); - this.ins.forEach(function (txin, i) { - txTmp.ins.push(new TransactionIn(txin)); - }); - this.outs.forEach(function (txout) { - txTmp.outs.push(new TransactionOut(txout)); - }); - txTmp.version = this.version; - txTmp.lock_time = this.lock_time; - - // In case concatenating two scripts ends up with two codeseparators, - // or an extra one at the end, this prevents all those possible - // incompatibilities. - script.findAndDelete(OP_CODESEPARATOR); - - // Get mode portion of hashtype - var hashTypeMode = hashType & 0x1f; - - // Generate modified transaction data for hash - var bytes = (new Put()); - bytes.word32le(this.version); - - // Serialize inputs - if (hashType & SIGHASH_ANYONECANPAY) { - // Blank out all inputs except current one, not recommended for open - // transactions. - bytes.varint(1); - bytes.put(this.ins[inIndex].o); - bytes.varint(script.buffer.length); - bytes.put(script.buffer); - bytes.word32le(this.ins[inIndex].q); - } else { - bytes.varint(this.ins.length); - for (var i = 0, l = this.ins.length; i < l; i++) { - var txin = this.ins[i]; - bytes.put(this.ins[i].o); + throw new Error("Input index '" + inIndex + "' invalid or out of bounds " + + "(" + this.ins.length + " inputs)"); + } - // Current input's script gets set to the script to be signed, all others - // get blanked. - if (inIndex === i) { - bytes.varint(script.buffer.length); - bytes.put(script.buffer); - } else { - bytes.varint(0); - } + // Clone transaction + var txTmp = new Transaction(); + this.ins.forEach(function(txin, i) { + txTmp.ins.push(new TransactionIn(txin)); + }); + this.outs.forEach(function(txout) { + txTmp.outs.push(new TransactionOut(txout)); + }); + txTmp.version = this.version; + txTmp.lock_time = this.lock_time; + + // In case concatenating two scripts ends up with two codeseparators, + // or an extra one at the end, this prevents all those possible + // incompatibilities. + script.findAndDelete(OP_CODESEPARATOR); + + // Get mode portion of hashtype + var hashTypeMode = hashType & 0x1f; + + // Generate modified transaction data for hash + var bytes = (new Put()); + bytes.word32le(this.version); + + // Serialize inputs + if (hashType & SIGHASH_ANYONECANPAY) { + // Blank out all inputs except current one, not recommended for open + // transactions. + bytes.varint(1); + bytes.put(this.ins[inIndex].o); + bytes.varint(script.buffer.length); + bytes.put(script.buffer); + bytes.word32le(this.ins[inIndex].q); + } else { + bytes.varint(this.ins.length); + for (var i = 0, l = this.ins.length; i < l; i++) { + var txin = this.ins[i]; + bytes.put(this.ins[i].o); + + // Current input's script gets set to the script to be signed, all others + // get blanked. + if (inIndex === i) { + bytes.varint(script.buffer.length); + bytes.put(script.buffer); + } else { + bytes.varint(0); + } - if (hashTypeMode === SIGHASH_NONE && inIndex !== i) { - bytes.word32le(0); - } else { - bytes.word32le(this.ins[i].q); + if (hashTypeMode === SIGHASH_NONE && inIndex !== i) { + bytes.word32le(0); + } else { + bytes.word32le(this.ins[i].q); + } } } - } - // Serialize outputs - if (hashTypeMode === SIGHASH_NONE) { - bytes.varint(0); - } else { - var outsLen; - if (hashTypeMode === SIGHASH_SINGLE) { - // TODO: Untested - if (inIndex >= txTmp.outs.length) { - throw new Error("Transaction.hashForSignature(): SIGHASH_SINGLE " + - "no corresponding txout found - out of bounds"); - } - outsLen = inIndex + 1; + // Serialize outputs + if (hashTypeMode === SIGHASH_NONE) { + bytes.varint(0); } else { - outsLen = this.outs.length; - } - - // TODO: If hashTypeMode !== SIGHASH_SINGLE, we could memcpy this whole - // section from the original transaction as is. - bytes.varint(outsLen); - for (var i = 0; i < outsLen; i++) { - if (hashTypeMode === SIGHASH_SINGLE && i !== inIndex) { - // Zero all outs except the one we want to keep - bytes.put(util.INT64_MAX); - bytes.varint(0); + var outsLen; + if (hashTypeMode === SIGHASH_SINGLE) { + // TODO: Untested + if (inIndex >= txTmp.outs.length) { + throw new Error("Transaction.hashForSignature(): SIGHASH_SINGLE " + + "no corresponding txout found - out of bounds"); + } + outsLen = inIndex + 1; } else { - bytes.put(this.outs[i].v); - bytes.varint(this.outs[i].s.length); - bytes.put(this.outs[i].s); + outsLen = this.outs.length; + } + + // TODO: If hashTypeMode !== SIGHASH_SINGLE, we could memcpy this whole + // section from the original transaction as is. + bytes.varint(outsLen); + for (var i = 0; i < outsLen; i++) { + if (hashTypeMode === SIGHASH_SINGLE && i !== inIndex) { + // Zero all outs except the one we want to keep + bytes.put(util.INT64_MAX); + bytes.varint(0); + } else { + bytes.put(this.outs[i].v); + bytes.varint(this.outs[i].s.length); + bytes.put(this.outs[i].s); + } } } - } - bytes.word32le(this.lock_time); + bytes.word32le(this.lock_time); - var buffer = bytes.buffer(); + var buffer = bytes.buffer(); - // Append hashType - buffer = Buffer.concat([buffer, new Buffer([parseInt(hashType), 0, 0, 0])]); + // Append hashType + buffer = Buffer.concat([buffer, new Buffer([parseInt(hashType), 0, 0, 0])]); - return util.twoSha256(buffer); + return util.twoSha256(buffer); }; /** @@ -565,7 +548,7 @@ Transaction.prototype.getStandardizedObject = function getStandardizedObject() { var totalSize = 8; // version + lock_time totalSize += util.getVarIntSize(this.ins.length); // tx_in count - var ins = this.ins.map(function (txin) { + var ins = this.ins.map(function(txin) { var txinObj = { prev_out: { hash: buffertools.reverse(new Buffer(txin.getOutpointHash())).toString('hex'), @@ -583,7 +566,7 @@ Transaction.prototype.getStandardizedObject = function getStandardizedObject() { }); totalSize += util.getVarIntSize(this.outs.length); - var outs = this.outs.map(function (txout) { + var outs = this.outs.map(function(txout) { totalSize += util.getVarIntSize(txout.s.length) + txout.s.length + 8; // script_len + script + value return { @@ -649,7 +632,7 @@ Transaction.prototype.fromObj = function fromObj(obj) { this.outs = txobj.outs; } -Transaction.prototype.parse = function (parser) { +Transaction.prototype.parse = function(parser) { if (Buffer.isBuffer(parser)) { this._buffer = parser; parser = new Parser(parser); @@ -664,10 +647,10 @@ Transaction.prototype.parse = function (parser) { this.ins = []; for (j = 0; j < txinCount; j++) { var txin = new TransactionIn(); - txin.o = parser.buffer(36); // outpoint - sLen = parser.varInt(); // script_len - txin.s = parser.buffer(sLen); // script - txin.q = parser.word32le(); // sequence + txin.o = parser.buffer(36); // outpoint + sLen = parser.varInt(); // script_len + txin.s = parser.buffer(sLen); // script + txin.q = parser.word32le(); // sequence this.ins.push(txin); } @@ -676,9 +659,9 @@ Transaction.prototype.parse = function (parser) { this.outs = []; for (j = 0; j < txoutCount; j++) { var txout = new TransactionOut(); - txout.v = parser.buffer(8); // value - sLen = parser.varInt(); // script_len - txout.s = parser.buffer(sLen); // script + txout.v = parser.buffer(8); // value + sLen = parser.varInt(); // script_len + txout.s = parser.buffer(sLen); // script this.outs.push(txout); } @@ -693,51 +676,51 @@ Transaction.prototype.parse = function (parser) { * * Selects some unspent outputs for later usage in tx inputs * - * @utxos + * @utxos * @totalNeededAmount: output transaction amount in BTC, including fee * @allowUnconfirmed: false (allow selecting unconfirmed utxos) * * Note that the sum of the selected unspent is >= the desired amount. - * Returns the selected unspent outputs if the totalNeededAmount was reach. + * Returns the selected unspent outputs if the totalNeededAmount was reach. * 'null' if not. * * TODO: utxo selection is not optimized to minimize mempool usage. * */ -Transaction.selectUnspent = function (utxos, totalNeededAmount, allowUnconfirmed) { +Transaction.selectUnspent = function(utxos, totalNeededAmount, allowUnconfirmed) { - var minConfirmationSteps = [6,1]; + var minConfirmationSteps = [6, 1]; if (allowUnconfirmed) minConfirmationSteps.push(0); var ret = []; var l = utxos.length; var totalSat = bignum(0); var totalNeededAmountSat = util.parseValue(totalNeededAmount); - var fulfill = false; + var fulfill = false; var maxConfirmations = null; do { var minConfirmations = minConfirmationSteps.shift(); - for(var i = 0; i=maxConfirmations) ) + if (c < minConfirmations || (maxConfirmations && c >= maxConfirmations)) continue; var sat = u.amountSat || util.parseValue(u.amount); totalSat = totalSat.add(sat); ret.push(u); - if(totalSat.cmp(totalNeededAmountSat) >= 0) { + if (totalSat.cmp(totalNeededAmountSat) >= 0) { fulfill = true; break; } } maxConfirmations = minConfirmations; - } while( !fulfill && minConfirmationSteps.length); + } while (!fulfill && minConfirmationSteps.length); //TODO(?): sort ret and check is some inputs can be avoided. //If the initial utxos are sorted, this step would be necesary only if @@ -748,11 +731,11 @@ Transaction.selectUnspent = function (utxos, totalNeededAmount, allowUnconfirmed /* * _scriptForAddress - * + * * Returns a scriptPubKey for the given address type */ -Transaction._scriptForAddress = function (addressString) { +Transaction._scriptForAddress = function(addressString) { var livenet = networks.livenet; var testnet = networks.testnet; @@ -774,7 +757,7 @@ Transaction._sumOutputs = function(outs) { var valueOutSat = bignum(0); var l = outs.length; - for(var i=0;i0) { + if (remainderSat.cmp(0) > 0) { var remainderAddress = opts.remainderAddress || ins[0].address; var value = util.bigIntToValue(remainderSat); var script = Transaction._scriptForAddress(remainderAddress); @@ -853,19 +836,19 @@ Transaction.createWithFee = function (ins, outs, feeSat, opts) { } - return new Transaction(txobj); + return new Transaction(txobj); }; -Transaction.prototype.calcSize = function () { +Transaction.prototype.calcSize = function() { var totalSize = 8; // version + lock_time totalSize += util.getVarIntSize(this.ins.length); // tx_in count - this.ins.forEach(function (txin) { + this.ins.forEach(function(txin) { totalSize += 36 + util.getVarIntSize(txin.s.length) + txin.s.length + 4; // outpoint + script_len + script + sequence }); totalSize += util.getVarIntSize(this.outs.length); - this.outs.forEach(function (txout) { + this.outs.forEach(function(txout) { totalSize += util.getVarIntSize(txout.s.length) + txout.s.length + 8; // script_len + script + value }); @@ -881,19 +864,19 @@ Transaction.prototype.getSize = function getHash() { }; -Transaction.prototype.isComplete = function () { +Transaction.prototype.isComplete = function() { var l = this.ins.length; var ret = true; - for (var i=0; i (maxSizeK+1)*1000 ); + selectedUtxos = Transaction + .selectUnspent(utxos, valueOutSat / util.COIN, opts.allowUnconfirmed); - return {tx: tx, selectedUtxos: selectedUtxos}; + if (!selectedUtxos) { + throw new Error( + 'the given UTXOs dont sum up the given outputs: ' + valueOutSat.toString() + ' (fee is ' + feeSat + ' )SAT' + ); + } + var tx = Transaction.createWithFee(selectedUtxos, outs, feeSat, { + remainderAddress: opts.remainderAddress, + lockTime: opts.lockTime, + }); + + size = tx.getSize(); + } while (size > (maxSizeK + 1) * 1000); + + return { + tx: tx, + selectedUtxos: selectedUtxos + }; }; @@ -1119,45 +1103,43 @@ Transaction.create = function (utxos, outs, opts) { * */ -Transaction.createAndSign = function (utxos, outs, keys, opts) { - var ret = Transaction.create(utxos, outs, opts); - ret.tx.sign(ret.selectedUtxos, keys); - return ret; +Transaction.createAndSign = function(utxos, outs, keys, opts) { + var ret = Transaction.create(utxos, outs, opts); + ret.tx.sign(ret.selectedUtxos, keys); + return ret; }; var TransactionInputsCache = exports.TransactionInputsCache = -function TransactionInputsCache(tx) -{ - var txList = []; - var txList64 = []; - var reqOuts = {}; - - // Get list of transactions required for verification - tx.ins.forEach(function (txin) { - if (txin.isCoinBase()) return; - - var hash = txin.o.slice(0, 32); - var hash64 = hash.toString('base64'); - if (txList64.indexOf(hash64) == -1) { - txList.push(hash); - txList64.push(hash64); - } - if (!reqOuts[hash64]) { - reqOuts[hash64] = []; - } - reqOuts[hash64][txin.getOutpointIndex()] = true; - }); + function TransactionInputsCache(tx) { + var txList = []; + var txList64 = []; + var reqOuts = {}; + + // Get list of transactions required for verification + tx.ins.forEach(function(txin) { + if (txin.isCoinBase()) return; + + var hash = txin.o.slice(0, 32); + var hash64 = hash.toString('base64'); + if (txList64.indexOf(hash64) == -1) { + txList.push(hash); + txList64.push(hash64); + } + if (!reqOuts[hash64]) { + reqOuts[hash64] = []; + } + reqOuts[hash64][txin.getOutpointIndex()] = true; + }); - this.tx = tx; - this.txList = txList; - this.txList64 = txList64; - this.txIndex = {}; - this.requiredOuts = reqOuts; - this.callbacks = []; + this.tx = tx; + this.txList = txList; + this.txList64 = txList64; + this.txIndex = {}; + this.requiredOuts = reqOuts; + this.callbacks = []; }; -TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, wait, callback) -{ +TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, wait, callback) { var self = this; var complete = false; @@ -1167,7 +1149,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w } var missingTx = {}; - self.txList64.forEach(function (hash64) { + self.txList64.forEach(function(hash64) { missingTx[hash64] = true; }); @@ -1176,10 +1158,10 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w if (err) throw err; // Index memory transactions - txs.forEach(function (tx) { + txs.forEach(function(tx) { var hash64 = tx.getHash().toString('base64'); var obj = {}; - Object.keys(self.requiredOuts[hash64]).forEach(function (o) { + Object.keys(self.requiredOuts[hash64]).forEach(function(o) { obj[+o] = tx.outs[+o]; }); self.txIndex[hash64] = obj; @@ -1206,7 +1188,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w // TODO: Major speedup should be possible if we load only the outs and not // whole transactions. var callback = this; - blockChain.getOutputsByHashes(self.txList, function (err, result) { + blockChain.getOutputsByHashes(self.txList, function(err, result) { callback(err, result); }); }, @@ -1216,7 +1198,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w var missingTxDbg = ''; if (Object.keys(missingTx).length) { - missingTxDbg = Object.keys(missingTx).map(function (hash64) { + missingTxDbg = Object.keys(missingTx).map(function(hash64) { return util.formatHash(new Buffer(hash64, 'base64')); }).join(','); } @@ -1224,11 +1206,10 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w if (wait && Object.keys(missingTx).length) { // TODO: This might no longer be needed now that saveTransactions uses // the safe=true option. - setTimeout(function () { + setTimeout(function() { var missingHashes = Object.keys(missingTx); if (missingHashes.length) { - self.callback(new Error('Missing inputs (timeout while searching): ' - + missingTxDbg)); + self.callback(new Error('Missing inputs (timeout while searching): ' + missingTxDbg)); } else if (!complete) { self.callback(new Error('Callback failed to trigger')); } @@ -1243,8 +1224,7 @@ TransactionInputsCache.prototype.buffer = function buffer(blockChain, txStore, w }; -TransactionInputsCache.prototype.callback = function callback(err) -{ +TransactionInputsCache.prototype.callback = function callback(err) { var args = Array.prototype.slice.apply(arguments); // Empty the callback array first (because downstream functions could add new @@ -1252,14 +1232,9 @@ TransactionInputsCache.prototype.callback = function callback(err) var cbs = this.callbacks; this.callbacks = []; - try { - cbs.forEach(function (cb) { - cb.apply(null, args); - }); - } catch (err) { - log.err("Callback error after connecting tx inputs: "+ - (err.stack ? err.stack : err.toString())); - } + cbs.forEach(function(cb) { + cb.apply(null, args); + }); }; module.exports = require('soop')(Transaction); diff --git a/test/test.Transaction.js b/test/test.Transaction.js index 9ab4022..fd165d1 100644 --- a/test/test.Transaction.js +++ b/test/test.Transaction.js @@ -24,17 +24,15 @@ function parse_test_transaction(entry) { // Ignore comments if (entry.length !== 3) return; - var inputs = []; + var inputs = {}; entry[0].forEach(function(vin) { - var hash = vin[0]; + var hash = (vin[0]); var index = vin[1]; var scriptPubKey = Script.fromHumanReadable(vin[2]); - inputs.push({ - 'prev_tx_hash': hash, - 'index': index, - 'scriptPubKey': scriptPubKey - }); + var mapKey = [hash, index]; + console.log('mapkey=' + mapKey); + inputs[mapKey] = scriptPubKey; }); @@ -341,53 +339,39 @@ describe('Transaction', function() { * Bitcoin core transaction tests */ // Verify that known valid transactions are intepretted correctly + var cb = function(err, results) { + should.not.exist(err); + should.exist(results); + results.should.equal(true); + }; testdata.dataTxValid.forEach(function(datum) { - var testTx = parse_test_transaction(datum); - if (!testTx) return; + if (datum.length < 3) return; + var raw = datum[1]; var verifyP2SH = datum[2]; - var transactionString = buffertools.toHex( - testTx.transaction.serialize()); - it('valid tx=' + transactionString, function() { + it('valid tx=' + raw, function() { // Verify that all inputs are valid - testTx.inputs.forEach(function(input) { + var testTx = parse_test_transaction(datum); + console.log(raw); + //buffertools.toHex(testTx.transaction.serialize()).should.equal(raw); + var inputs = testTx.transaction.inputs(); + for (var i = 0; i < inputs.length; i++) { + console.log(' input number #########' + i); + var input = inputs[i]; + buffertools.reverse(input[0]); + input[0] = buffertools.toHex(input[0]); + var mapKey = [input]; + var scriptPubKey = testTx.inputs[mapKey]; + if (!scriptPubKey) throw new Error('asdasdasdasd'); testTx.transaction.verifyInput( - input.index, - input.scriptPubKey, - { verifyP2SH: verifyP2SH, dontVerifyStrictEnc: true}, - function(err, results) { - // Exceptions raised inside this function will be handled - // ...by this function, so ignore if that is the case - if (err && err.constructor.name === 'AssertionError') return; - - should.not.exist(err); - should.exist(results); - results.should.equal(true); - }); - }); + i, + scriptPubKey, { + verifyP2SH: verifyP2SH, + dontVerifyStrictEnc: true + }, + cb); + } }); }); - // Verify that known invalid transactions are interpretted correctly - testdata.dataTxInvalid.forEach(function(datum) { - var testTx = parse_test_transaction(datum); - if (!testTx) return; - var transactionString = buffertools.toHex( - testTx.transaction.serialize()); - - it('valid tx=' + transactionString, function() { - // Verify that all inputs are invalid - testTx.inputs.forEach(function(input) { - testTx.transaction.verifyInput(input.index, input.scriptPubKey, - function(err, results) { - // Exceptions raised inside this function will be handled - // ...by this function, so ignore if that is the case - if (err && err.constructor.name === 'AssertionError') return; - - // There should either be an error, or the results should be false. - (err !== null || (!err && results === false)).should.equal(true); - }); - }); - }); - }); }); diff --git a/util/util.js b/util/util.js index 7a33869..785ae30 100644 --- a/util/util.js +++ b/util/util.js @@ -1,54 +1,51 @@ - var crypto = require('crypto'); var bignum = require('bignum'); var Binary = require('binary'); var Put = require('bufferput'); var buffertools = require('buffertools'); var browser; -if (!process.versions) { - // browser version +var inBrowser = !process.versions; +if (inBrowser) { browser = require('../browser/vendor-bundle.js'); } -var sha256 = exports.sha256 = function (data) { +var sha256 = exports.sha256 = function(data) { return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary'); }; -var ripe160 = exports.ripe160 = function (data) { +var ripe160 = exports.ripe160 = function(data) { if (!Buffer.isBuffer(data)) { throw new Error('arg should be a buffer'); } - - if (!process.versions) { - - var w = new browser.crypto31.lib.WordArray.init(Crypto.util.bytesToWords(data), data.length); + if (inBrowser) { + var w = new browser.crypto31.lib.WordArray.init(browser.Crypto.util.bytesToWords(data), data.length); var wordArray = browser.crypto31.RIPEMD160(w); var words = wordArray.words; var answer = []; for (var b = 0; b < words.length * 32; b += 8) { - answer.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); + answer.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); } return new Buffer(answer, 'hex'); } return new Buffer(crypto.createHash('rmd160').update(data).digest('binary'), 'binary'); }; -var sha1 = exports.sha1 = function (data) { +var sha1 = exports.sha1 = function(data) { return new Buffer(crypto.createHash('sha1').update(data).digest('binary'), 'binary'); }; -var twoSha256 = exports.twoSha256 = function (data) { +var twoSha256 = exports.twoSha256 = function(data) { return sha256(sha256(data)); }; -var sha256ripe160 = exports.sha256ripe160 = function (data) { +var sha256ripe160 = exports.sha256ripe160 = function(data) { return ripe160(sha256(data)); }; /** * Format a block hash like the official client does. */ -var formatHash = exports.formatHash = function (hash) { +var formatHash = exports.formatHash = function(hash) { var hashEnd = new Buffer(10); hash.copy(hashEnd, 0, 22, 32); return buffertools.reverse(hashEnd).toString('hex'); @@ -57,7 +54,7 @@ var formatHash = exports.formatHash = function (hash) { /** * Display the whole hash, as hex, in correct endian order. */ -var formatHashFull = exports.formatHashFull = function (hash) { +var formatHashFull = exports.formatHashFull = function(hash) { var copy = new Buffer(hash.length); hash.copy(copy); var hex = buffertools.toHex(buffertools.reverse(copy)); @@ -69,13 +66,13 @@ var formatHashFull = exports.formatHashFull = function (hash) { * * Formats a block hash by removing leading zeros and truncating to 10 characters. */ -var formatHashAlt = exports.formatHashAlt = function (hash) { +var formatHashAlt = exports.formatHashAlt = function(hash) { var hex = formatHashFull(hash); hex = hex.replace(/^0*/, ''); return hex.substr(0, 10); }; -var formatBuffer = exports.formatBuffer = function (buffer, maxLen) { +var formatBuffer = exports.formatBuffer = function(buffer, maxLen) { // Calculate amount of bytes to display if (maxLen === null) { maxLen = 10; @@ -96,41 +93,47 @@ var formatBuffer = exports.formatBuffer = function (buffer, maxLen) { return output; }; -var valueToBigInt = exports.valueToBigInt = function (valueBuffer) { +var valueToBigInt = exports.valueToBigInt = function(valueBuffer) { if (Buffer.isBuffer(valueBuffer)) { - return bignum.fromBuffer(valueBuffer, {endian: 'little', size: 8}); + return bignum.fromBuffer(valueBuffer, { + endian: 'little', + size: 8 + }); } else { return valueBuffer; } }; -var bigIntToValue = exports.bigIntToValue = function (valueBigInt) { +var bigIntToValue = exports.bigIntToValue = function(valueBigInt) { if (Buffer.isBuffer(valueBigInt)) { return valueBigInt; } else { - return valueBigInt.toBuffer({endian: 'little', size: 8}); + return valueBigInt.toBuffer({ + endian: 'little', + size: 8 + }); } }; var fitsInNBits = function(integer, n) { // TODO: make this efficient!!! - return integer.toString(2).replace('-','').length < n; + return integer.toString(2).replace('-', '').length < n; }; exports.bytesNeededToStore = bytesNeededToStore = function(integer) { if (integer === 0) return 0; - return Math.ceil(((integer).toString(2).replace('-','').length + 1)/ 8); + return Math.ceil(((integer).toString(2).replace('-', '').length + 1) / 8); }; exports.negativeBuffer = negativeBuffer = function(b) { // implement two-complement negative var c = new Buffer(b.length); // negate each byte - for (var i=0; i=0; i--){ + for (var i = b.length - 1; i >= 0; i--) { c[i] += 1; if (c[i] >= 256) c[i] -= 256; if (c[i] !== 0) break; @@ -141,7 +144,7 @@ exports.negativeBuffer = negativeBuffer = function(b) { /* * Transforms an integer into a buffer using two-complement encoding * For example, 1 is encoded as 01 and -1 is encoded as ff - * For more info see: + * For more info see: * http://en.wikipedia.org/wiki/Signed_number_representations#Two.27s_complement */ exports.intToBuffer2C = function(integer) { @@ -149,9 +152,9 @@ exports.intToBuffer2C = function(integer) { var buf = new Put(); var s = integer.toString(16); var neg = s[0] === '-'; - s = s.replace('-',''); - for (var i=0; i 8 ? value.substr(0, value.length-8) : '0'; - var decimalPart = value.length > 8 ? value.substr(value.length-8) : value; + var integerPart = value.length > 8 ? value.substr(0, value.length - 8) : '0'; + var decimalPart = value.length > 8 ? value.substr(value.length - 8) : value; while (decimalPart.length < 8) { - decimalPart = "0"+decimalPart; + decimalPart = "0" + decimalPart; } decimalPart = decimalPart.replace(/0*$/, ''); while (decimalPart.length < 2) { decimalPart += "0"; } - return integerPart+"."+decimalPart; + return integerPart + "." + decimalPart; }; var reFullVal = /^\s*(\d+)\.(\d+)/; var reFracVal = /^\s*\.(\d+)/; var reWholeVal = /^\s*(\d+)/; -function padFrac(frac) -{ - frac=frac.substr(0,8); //truncate to 8 decimal places +function padFrac(frac) { + frac = frac.substr(0, 8); //truncate to 8 decimal places while (frac.length < 8) frac = frac + '0'; return frac; } -function parseFullValue(res) -{ +function parseFullValue(res) { return bignum(res[1]).mul('100000000').add(padFrac(res[2])); } -function parseFracValue(res) -{ +function parseFracValue(res) { return bignum(padFrac(res[1])); } -function parseWholeValue(res) -{ +function parseWholeValue(res) { return bignum(res[1]).mul('100000000'); } -exports.parseValue = function parseValue(valueStr) -{ - if (typeof valueStr !== 'string') +exports.parseValue = function parseValue(valueStr) { + if (typeof valueStr !== 'string') valueStr = valueStr.toString(); var res = valueStr.match(reFullVal); @@ -296,11 +294,11 @@ exports.parseValue = function parseValue(valueStr) }; // Utility that synchronizes function calls based on a key -var createSynchrotron = exports.createSynchrotron = function (fn) { +var createSynchrotron = exports.createSynchrotron = function(fn) { var table = {}; - return function (key) { + return function(key) { var args = Array.prototype.slice.call(arguments); - var run = function () { + var run = function() { // Function fn() will call when it finishes args[0] = function next() { if (table[key]) { @@ -333,21 +331,23 @@ var createSynchrotron = exports.createSynchrotron = function (fn) { * * @returns Buffer random nonce */ -var generateNonce = exports.generateNonce = function () { - var b32 = 0x100000000, ff = 0xff; - var b = new Buffer(8), i = 0; +var generateNonce = exports.generateNonce = function() { + var b32 = 0x100000000, + ff = 0xff; + var b = new Buffer(8), + i = 0; // Generate eight random bytes - r = Math.random()*b32; + r = Math.random() * b32; b[i++] = r & ff; - b[i++] = (r=r>>>8) & ff; - b[i++] = (r=r>>>8) & ff; - b[i++] = (r=r>>>8) & ff; - r = Math.random()*b32; + b[i++] = (r = r >>> 8) & ff; + b[i++] = (r = r >>> 8) & ff; + b[i++] = (r = r >>> 8) & ff; + r = Math.random() * b32; b[i++] = r & ff; - b[i++] = (r=r>>>8) & ff; - b[i++] = (r=r>>>8) & ff; - b[i++] = (r=r>>>8) & ff; + b[i++] = (r = r >>> 8) & ff; + b[i++] = (r = r >>> 8) & ff; + b[i++] = (r = r >>> 8) & ff; return b; }; @@ -357,10 +357,10 @@ var generateNonce = exports.generateNonce = function () { * * This function calculates the difficulty target given the difficulty bits. */ -var decodeDiffBits = exports.decodeDiffBits = function (diffBits, asBigInt) { +var decodeDiffBits = exports.decodeDiffBits = function(diffBits, asBigInt) { diffBits = +diffBits; var target = bignum(diffBits & 0xffffff); - target = target.shiftLeft(8*((diffBits >>> 24) - 3)); + target = target.shiftLeft(8 * ((diffBits >>> 24) - 3)); if (asBigInt) { return target; @@ -370,7 +370,7 @@ var decodeDiffBits = exports.decodeDiffBits = function (diffBits, asBigInt) { var diffBuf = target.toBuffer(); var targetBuf = new Buffer(32); buffertools.fill(targetBuf, 0); - diffBuf.copy(targetBuf, 32-diffBuf.length); + diffBuf.copy(targetBuf, 32 - diffBuf.length); return targetBuf; }; @@ -393,8 +393,8 @@ var encodeDiffBits = exports.encodeDiffBits = function encodeDiffBits(target) { var compact = size << 24; if (size >= 1) compact |= mpiBuf[4] << 16; - if (size >= 2) compact |= mpiBuf[5] << 8; - if (size >= 3) compact |= mpiBuf[6] ; + if (size >= 2) compact |= mpiBuf[5] << 8; + if (size >= 3) compact |= mpiBuf[6]; return compact; }; @@ -405,16 +405,20 @@ var encodeDiffBits = exports.encodeDiffBits = function encodeDiffBits(target) { * This function calculates the maximum difficulty target divided by the given * difficulty target. */ -var calcDifficulty = exports.calcDifficulty = function (target) { +var calcDifficulty = exports.calcDifficulty = function(target) { if (!Buffer.isBuffer(target)) { target = decodeDiffBits(target); } - var targetBigint = bignum.fromBuffer(target, {order: 'forward'}); - var maxBigint = bignum.fromBuffer(MAX_TARGET, {order: 'forward'}); + var targetBigint = bignum.fromBuffer(target, { + order: 'forward' + }); + var maxBigint = bignum.fromBuffer(MAX_TARGET, { + order: 'forward' + }); return maxBigint.div(targetBigint).toNumber(); }; -var reverseBytes32 = exports.reverseBytes32 = function (data) { +var reverseBytes32 = exports.reverseBytes32 = function(data) { if (data.length % 4) { throw new Error("Util.reverseBytes32(): Data length must be multiple of 4"); }