diff --git a/Script.js b/Script.js index b46a868..a4ffbd2 100644 --- a/Script.js +++ b/Script.js @@ -74,9 +74,12 @@ Script.prototype.parse = function() { }; Script.prototype.isPushOnly = function() { - for (var i = 0; i < this.chunks.length; i++) - if (!Buffer.isBuffer(this.chunks[i])) + for (var i = 0; i < this.chunks.length; i++) { + var op = this.chunks[i]; + if (!Buffer.isBuffer(op) && op > OP_16) { return false; + } + } return true; }; diff --git a/ScriptInterpreter.js b/ScriptInterpreter.js index ca5db71..d9a0c21 100644 --- a/ScriptInterpreter.js +++ b/ScriptInterpreter.js @@ -48,717 +48,724 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, executeStep.call(this, callback); function executeStep(cb) { - // Once all chunks have been processed, execution ends - if (pc >= script.chunks.length) { - // Execution stack must be empty at the end of the script - if (execStack.length) { - cb(new Error("Execution stack ended non-empty")); + try { + // Once all chunks have been processed, execution ends + if (pc >= script.chunks.length) { + // Execution stack must be empty at the end of the script + if (execStack.length) { + cb(new Error("Execution stack ended non-empty")); + return; + } + + // Execution successful (Note that we still have to check whether the + // final stack contains a truthy value.) + cb(null); return; } - // Execution successful (Note that we still have to check whether the - // final stack contains a truthy value.) - cb(null); - return; - } + // 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.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 (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 (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; - 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; + case OP_ELSE: + if (execStack.length < 1) { + throw new Error("Unmatched OP_ELSE"); } - } - 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) { + 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"); + + 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(); - } 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.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_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_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]; + 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"); } - } else if (opcode === OP_OR) { - 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"); } - } else if (opcode === OP_XOR) { - for (var i = 0, l = out.length; i < l; i++) { - out[i] = v1[i] ^ v2[i]; + if ((start + len) >= buf.length) { + throw new Error("OP_SUBSTR range out of bounds"); } - } - 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(); + 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 { - throw new Error("OP_EQUALVERIFY negative"); + 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_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"); + 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]; } - num = v1.shiftLeft(v2); - break; + } 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); - case OP_RSHIFT: - if (v2.cmp(0) < 0 || v2.cmp(2048) > 0) { - throw new Error("OP_RSHIFT parameter out of bounds"); + 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 { + throw new Error("OP_EQUALVERIFY negative"); } - 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_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); - - // check canonical signature - 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; + 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"); + } + num = v1.shiftLeft(v2); + break; - // Update stack + 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(new Buffer([success ? 1 : 0])); - if (opcode === OP_CHECKSIGVERIFY) { - if (success) { + this.stack.push(intToBufferSM(num)); + + if (opcode === OP_NUMEQUALVERIFY) { + if (castBool(this.stackTop())) { this.stackPop(); } else { - throw new Error("OP_CHECKSIGVERIFY negative"); + throw new Error("OP_NUMEQUALVERIFY 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; - // Run next step - executeStep.call(this, cb); - }.bind(this)); + case OP_CODESEPARATOR: + // Hash starts after the code separator + hashStart = pc; + break; - // Note that for asynchronous opcodes we have to return here to prevent - // the next opcode from being executed. - return; + case OP_CHECKSIG: + case OP_CHECKSIGVERIFY: + // (sig pubkey -- bool) + var sig = this.stackTop(2); + var pubkey = this.stackTop(1); - 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)); + // 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); - }); - - 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 { - ikey++; - keysCount--; - // If there are more signatures than keys left, then too many - // signatures have failed - if (sigsCount > keysCount) { - success = false; - } - } + // check canonical signature + this.isCanonicalSignature(new Buffer(sig)); - checkMultiSigStep.call(this); - }.bind(this)); - } else { + // 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; + } + + // Update stack + this.stackPop(); + this.stackPop(); this.stack.push(new Buffer([success ? 1 : 0])); - if (opcode === OP_CHECKMULTISIGVERIFY) { + if (opcode === OP_CHECKSIGVERIFY) { if (success) { this.stackPop(); } else { - throw new Error("OP_CHECKMULTISIGVERIFY negative"); + throw new Error("OP_CHECKSIGVERIFY negative"); } } // 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++) { + var pubkey = this.stackPop() + keys.push(pubkey); } - }; - // Note that for asynchronous opcodes we have to return here to prevent - // the next opcode from being executed. - return; + 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()); + } - default: - throw new Error("Unknown opcode encountered"); - } + // 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(); - // Size limits - if ((this.stack.length + altStack.length) > 1000) { - throw new Error("Maximum stack size exceeded"); - } + // 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); + + var that = this; + sigs.forEach(function(sig) { + // check each signature is canonical + that.isCanonicalSignature(new Buffer(sig)); + // Drop the signatures for the subscript, since a signature can't sign itself + scriptCode.findAndDelete(sig); + }); + + var success = true, + isig = 0, + ikey = 0; + checkMultiSigStep.call(this); + + function checkMultiSigStep() { + if (success && sigsCount > 0) { + var sig = sigs[isig]; + var pubkey = keys[ikey]; + + checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) { + 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; + } + } - // 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); + checkMultiSigStep.call(this); + }.bind(this)); + } 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); + } + }; + + // Note that for asynchronous opcodes we have to return here to prevent + // the next opcode from being executed. + return; + + default: + throw new Error("Unknown opcode encountered"); + } + + // 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); + } + } catch (e) { + cb(e); } } }; @@ -866,9 +873,9 @@ ScriptInterpreter.prototype.getResult = function getResult() { return castBool(this.stack[this.stack.length - 1]); }; -// Use ScriptInterpreter.verifyFull instead +// WARN: Use ScriptInterpreter.verifyFull instead ScriptInterpreter.verify = - function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) { + function verify(scriptSig, scriptPubKey, tx, n, hashType, callback) { if ("function" !== typeof callback) { throw new Error("ScriptInterpreter.verify() requires a callback"); } @@ -877,7 +884,7 @@ ScriptInterpreter.verify = var si = new ScriptInterpreter(); // Evaluate scripts - si.evalTwo(scriptSig, scriptPubKey, txTo, n, hashType, function(err) { + si.evalTwo(scriptSig, scriptPubKey, tx, n, hashType, function(err) { if (err) { callback(err); return; @@ -892,8 +899,8 @@ ScriptInterpreter.verify = return si; }; -ScriptInterpreter.prototype.verifyStep4 = function(scriptSig, scriptPubKey, - txTo, nIn, hashType, callback, siCopy) { +ScriptInterpreter.prototype.verifyStep4 = function(callback, siCopy) { + // 4th step, check P2SH subscript evaluated to true if (siCopy.stack.length == 0) { callback(null, false); return; @@ -903,91 +910,105 @@ ScriptInterpreter.prototype.verifyStep4 = function(scriptSig, scriptPubKey, } ScriptInterpreter.prototype.verifyStep3 = function(scriptSig, - scriptPubKey, txTo, nIn, hashType, callback, siCopy) { - if (this.stack.length == 0) { + scriptPubKey, tx, nIn, hashType, callback, siCopy) { + + // 3rd step, check result (stack should contain true) + + // if stack is empty, script considered invalid + if (this.stack.length === 0) { callback(null, false); return; } + + // if top of stack contains false, script evaluated to false if (castBool(this.stackBack()) == false) { callback(null, false); return; } - // if not P2SH, we're done + // if not P2SH, script evaluated to true if (!this.opts.verifyP2SH || !scriptPubKey.isP2SH()) { callback(null, true); return; } + // if P2SH, scriptSig should be push-only if (!scriptSig.isPushOnly()) { callback(null, false); return; } - if (siCopy.length === 0) - throw new Error('siCopy should have length != 0'); + // P2SH script should exist + if (siCopy.length === 0) { + throw new Error('siCopy should have length != 0'); + } var subscript = new Script(siCopy.stackPop()); - var that = this; - siCopy.eval(subscript, txTo, nIn, hashType, function(err) { - if (err) callback(err); - else that.verifyStep4(scriptSig, scriptPubKey, txTo, nIn, - hashType, callback, siCopy); + // evaluate the P2SH subscript + siCopy.eval(subscript, tx, nIn, hashType, function(err) { + if (err) return callback(err); + that.verifyStep4(callback, siCopy); }); }; ScriptInterpreter.prototype.verifyStep2 = function(scriptSig, scriptPubKey, - txTo, nIn, hashType, callback, siCopy) { + tx, nIn, hashType, callback, siCopy) { + var siCopy; if (this.opts.verifyP2SH) { + siCopy = new ScriptInterpreter(this.opts); this.stack.forEach(function(item) { siCopy.stack.push(item); }); } var that = this; - this.eval(scriptPubKey, txTo, nIn, hashType, function(err) { - if (err) callback(err); - else that.verifyStep3(scriptSig, scriptPubKey, txTo, nIn, + // 2nd step, evaluate scriptPubKey + this.eval(scriptPubKey, tx, nIn, hashType, function(err) { + if (err) return callback(err); + that.verifyStep3(scriptSig, scriptPubKey, tx, nIn, hashType, callback, siCopy); }); }; +ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey, + tx, nIn, hashType, callback) { + var that = this; + + // 1st step, evaluate scriptSig + this.eval(scriptSig, tx, nIn, hashType, function(err) { + if (err) return callback(err); + that.verifyStep2(scriptSig, scriptPubKey, tx, nIn, + hashType, callback); + }); +}; + ScriptInterpreter.verifyFull = - function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType, + function verifyFull(scriptSig, scriptPubKey, tx, nIn, hashType, opts, callback) { var si = new ScriptInterpreter(opts); si.verifyFull(scriptSig, scriptPubKey, - txTo, nIn, hashType, callback); + tx, nIn, hashType, callback); }; -ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey, - txTo, nIn, hashType, callback) { - var siCopy = new ScriptInterpreter(this.opts); - 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); - } - }); - -}; var checkSig = ScriptInterpreter.checkSig = function(sig, pubkey, scriptCode, tx, n, hashType, callback) { + // https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works if (!sig.length) { callback(null, false); return; } - if (hashType == 0) { + // If the hash-type value is 0, then it is replaced by the last_byte of the signature. + if (hashType === 0) { hashType = sig[sig.length - 1]; } else if (hashType != sig[sig.length - 1]) { callback(null, false); return; } + + // Then the last byte of the signature is always deleted. (hashType removed) sig = sig.slice(0, sig.length - 1); // Signature verification requires a special hash procedure @@ -995,7 +1016,9 @@ var checkSig = ScriptInterpreter.checkSig = // Verify signature var key = new Key(); + if (pubkey.length === 0) pubkey = new Buffer('00', 'hex'); key.public = pubkey; + key.verifySignature(hash, sig, callback); }; diff --git a/Transaction.js b/Transaction.js index 33196b5..fe06563 100644 --- a/Transaction.js +++ b/Transaction.js @@ -64,7 +64,6 @@ TransactionIn.prototype.getOutpointHash = function getOutpointHash() { if ("undefined" !== typeof this.o.outHashCache) { return this.o.outHashCache; } - return this.o.outHashCache = this.o.slice(0, 32); }; @@ -385,8 +384,9 @@ Transaction.Serializer = TransactionSignatureSerializer; var oneBuffer = function() { // bug present in bitcoind which must be also present in bitcore // see https://bitcointalk.org/index.php?topic=260595 - var ret = new Buffer(1); + var ret = new Buffer(32); ret.writeUInt8(1, 0); + for (var i=1; i<32; i++) ret.writeUInt8(0, i); return ret; // return 1 bug }; @@ -412,7 +412,7 @@ Transaction.prototype.hashForSignature = // Append hashType var hashBuf = new Put().word32le(hashType).buffer(); buffer = Buffer.concat([buffer, hashBuf]); - return buffertools.reverse(util.twoSha256(buffer)); + return util.twoSha256(buffer); }; /** diff --git a/src/eckey.cc b/src/eckey.cc index 9f0886e..973d989 100644 --- a/src/eckey.cc +++ b/src/eckey.cc @@ -309,8 +309,9 @@ Key::SetPublic(Local property, Local value, const AccessorInfo& i Key* key = node::ObjectWrap::Unwrap(info.Holder()); Handle buffer = value->ToObject(); const unsigned char *data = (const unsigned char*) Buffer::Data(buffer); + ec_key_st* ret = o2i_ECPublicKey(&(key->ec), &data, Buffer::Length(buffer)); - if (!o2i_ECPublicKey(&(key->ec), &data, Buffer::Length(buffer))) { + if (!ret) { // TODO: Error return; } diff --git a/test/data/sighash.json b/test/data/sighash.json index d66a56a..bd2a839 100644 --- a/test/data/sighash.json +++ b/test/data/sighash.json @@ -1,5 +1,6 @@ [ ["raw_transaction, script, input_index, hashType, signature_hash (result)"], + ["0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae", 0, 1, "c21469f396d266507fd339292bd8ff0a6d4b29538b914265387a4d17e4839d25"], ["907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000004ab65ababfd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de802000000096aab5253ab52000052ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000009ab53526500636a52ab599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec229", "", 2, 1864164639, "31af167a6cf3f9d5f6875caa4d31704ceb0eba078d132b78dab52c3b8997317e"], ["a0aa3126041621a6dea5b800141aa696daf28408959dfb2df96095db9fa425ad3f427f2f6103000000015360290e9c6063fa26912c2e7fb6a0ad80f1c5fea1771d42f12976092e7a85a4229fdb6e890000000001abc109f6e47688ac0e4682988785744602b8c87228fcef0695085edf19088af1a9db126e93000000000665516aac536affffffff8fe53e0806e12dfd05d67ac68f4768fdbe23fc48ace22a5aa8ba04c96d58e2750300000009ac51abac63ab5153650524aa680455ce7b000000000000499e50030000000008636a00ac526563ac5051ee030000000003abacabd2b6fe000000000003516563910fb6b5", "65", 0, -1391424484, "48d6a1bd2cd9eec54eb866fc71209418a950402b5d7e52363bfb75c98e141175"], ["6e7e9d4b04ce17afa1e8546b627bb8d89a6a7fefd9d892ec8a192d79c2ceafc01694a6a7e7030000000953ac6a51006353636a33bced1544f797f08ceed02f108da22cd24c9e7809a446c61eb3895914508ac91f07053a01000000055163ab516affffffff11dc54eee8f9e4ff0bcf6b1a1a35b1cd10d63389571375501af7444073bcec3c02000000046aab53514a821f0ce3956e235f71e4c69d91abe1e93fb703bd33039ac567249ed339bf0ba0883ef300000000090063ab65000065ac654bec3cc504bcf499020000000005ab6a52abac64eb060100000000076a6a5351650053bbbc130100000000056a6aab53abd6e1380100000000026a51c4e509b8", "acab655151", 0, 479279909, "2a3d95b09237b72034b23f2d2bb29fa32a58ab5c6aa72f6aafdfa178ab1dd01c"], diff --git a/test/test.Transaction.js b/test/test.Transaction.js index 8e37f53..70d93dd 100644 --- a/test/test.Transaction.js +++ b/test/test.Transaction.js @@ -67,37 +67,53 @@ describe('Transaction', function() { */ // Verify that known valid transactions are intepretted correctly var coreTest = function(data, valid) { + buffertools.extend(); data.forEach(function(datum) { if (datum.length < 3) return; var raw = datum[1]; var verifyP2SH = datum[2]; - - it.skip((valid ? '' : 'in') + 'valid tx=' + raw, function(done) { - var cb = function(err, results) { - should.not.exist(err); - should.exist(results); - results.should.equal(valid); - done(); - }; - - var testTx = parse_test_transaction(datum); - buffertools.toHex(testTx.transaction.serialize()).should.equal(raw); - var inputs = testTx.transaction.inputs(); - for (var i = 0; i < inputs.length; 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('Bad test: '+datum); - testTx.transaction.verifyInput( - i, - scriptPubKey, { - verifyP2SH: verifyP2SH, - dontVerifyStrictEnc: true - }, - cb); - } + var testTx = parse_test_transaction(datum); + var tx = testTx.transaction; + + describe((valid ? '' : 'in') + 'valid tx=' + raw, function() { + it('should parse correctly', function() { + buffertools.toHex(tx.serialize()).toLowerCase().should.equal(raw.toLowerCase()); + }); + + var inputs = tx.inputs(); + var j = 0; + inputs.forEach(function(input) { + var i = j; + j += 1; + it('should validate input #' + i, function(done) { + + var outpointHash = new Buffer(input[0].length); + input[0].copy(outpointHash); + input[0] = buffertools.reverse(outpointHash); + input[0] = buffertools.toHex(input[0]); + var mapKey = [input]; + var scriptPubKey = testTx.inputs[mapKey]; + if (!scriptPubKey) throw new Error('Bad test: ' + datum); + tx.verifyInput( + i, + scriptPubKey, { + verifyP2SH: verifyP2SH, + dontVerifyStrictEnc: true + }, + function(err, results) { + if (valid) { + should.not.exist(err); + should.exist(results); + results.should.equal(valid); + } else { + var invalid = (typeof err !== 'undefined') || results === false; + invalid.should.equal(true); + } + done(); + } + ); + }); + }); }); }); }; diff --git a/test/test.sighash.js b/test/test.sighash.js index 77ad82b..e86a1b5 100644 --- a/test/test.sighash.js +++ b/test/test.sighash.js @@ -98,8 +98,9 @@ var randomTx = function(single) { var oneBuffer = function() { // bug present in bitcoind which must be also present in bitcore // see https://bitcointalk.org/index.php?topic=260595 - var ret = new Buffer(1); + var ret = new Buffer(32); ret.writeUInt8(1, 0); + for (var i=1; i<32; i++) ret.writeUInt8(0, i); return ret; // return 1 bug }; @@ -125,7 +126,7 @@ var signatureHashOld = function(tx, script, inIndex, hashType) { // Append hashType var hashBuf = new Put().word32le(hashType).buffer(); buffer = Buffer.concat([buffer, hashBuf]); - return buffertools.reverse(util.twoSha256(buffer)); + return util.twoSha256(buffer); }; @@ -156,7 +157,7 @@ describe('Transaction sighash (#hashForSignature)', function() { var scriptPubKey = new Script(new Buffer(datum[1], 'hex')); var input_index = parseInt(datum[2]); var hashType = parseInt(datum[3]); - var sighash = datum[4]; + var sighash = buffertools.toHex(buffertools.reverse(new Buffer(datum[4],'hex'))); it('should validate correctly ' + buffertools.toHex(raw_tx), function() { var tx = new Transaction(); tx.parse(raw_tx);