diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index 31e132f..e7e164b 100644 --- a/lib/script/interpreter.js +++ b/lib/script/interpreter.js @@ -31,6 +31,70 @@ var Interpreter = function Interpreter(obj) { } }; +Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, satoshis, flags) { + var scriptPubKey = new Script; + var stack = []; + + if (version == 0) { + if (program.length == 32) { + if (witness.length == 0) { + this.errstr = 'v0 scripthash program empty'; + return false; + } + + scriptPubKey = witness[witness.length - 1]; + var hash = Hash.sha256(scriptPubKey); + if (hash !== program.script) { + this.errstr = 'witness program mismatch'; + return false; + } + + stack = witness.slice(0, -1); + } else if (program.script.length == 20) { + if (witness.length != 2) { + this.errstr = 'witness program mismatch'; + return false; + } + + scriptPubKey.add(Opcode.OP_DUP).add(Opcode.OP_HASH160).add(program.script).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIG); + stack = witness; + } else { + this.errstr = 'Witness program wrong length'; + return false; + } + } else if ((flags & this.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { + this.errstr = 'Upgradeable witness program discouraged'; + return false; + } else { + return true; + } + + this.set({ + script: scriptPubKey, + stack: stack, + sigversion: 1, + satoshis: satoshis + }); + + if (!this.evaluate()) { + return false; + } + + if (this.stack.length !== 1) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } + var buf = this.stack[this.stack.length - 1]; + if (!Interpreter.castToBool(buf)) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; + return false; + } + + return true; +}; + + + /** * Verifies a Script by executing it and returns true if it is valid. * This function needs to be provided with the scriptSig and the scriptPubkey @@ -41,10 +105,13 @@ var Interpreter = function Interpreter(obj) { * to check signature validity for some opcodes like OP_CHECKSIG) * @param {number} nin - index of the transaction input containing the scriptSig verified. * @param {number} flags - evaluation flags. See Interpreter.SCRIPT_* constants + * @param {number} witness - array of witness data + * @param {number} satoshis - number of satoshis created by this output * * Translated from bitcoind's VerifyScript */ -Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) { + Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) { + var Transaction = require('../transaction'); if (_.isUndefined(tx)) { tx = new Transaction(); @@ -55,10 +122,19 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) if (_.isUndefined(flags)) { flags = 0; } + if (_.isUndefined(witness)) { + witness = null; + } + if (_.isUndefined(satoshis)) { + satoshis = 0; + } + this.set({ script: scriptSig, tx: tx, nin: nin, + sigversion: 0, + satoshis: 0, flags: flags }); var stackCopy; @@ -103,6 +179,24 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) return false; } + var hadWitness = false; + var version, program; + + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + if (scriptPubkey.isWitnessProgram()) { + version = scriptPubkey[0]; + program = s.toProgram(); + hadWitness = true; + if (scriptSig.toBuffer().length != 0) { + return false; + } + + if (!this.verifyWitnessProgram(version, program, witness, satoshis, flags)) { + return false; + } + } + } + // Additional validation for spend-to-script-hash transactions: if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) { // scriptSig must be literals-only or validation fails @@ -144,8 +238,30 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; return false; - } else { - return true; + } + if ((flags & this.SCRIPT_VERIFY_WITNESS)) { + if (redeemScript.isWitnessOut()) { + version = redeemScript[0]; + program = redeemScript.toProgram(); + hadWitness = true; + if (scriptSig !== redeemScript) { + this.errstr = 'Malleated scriptSig'; + return false; + } + + if (!this.verifyWitnessProgram(version, program, witness, satoshis, flags)) { + return false; + } + + stack = [stack[0]]; + } + } + + if ((flags & this.SCRIPT_VERIFY_WITNESS)) { + if (!hadWitness && witness.length > 0) { + this.errstr = 'Witness unexpected'; + return false; + } } } @@ -158,6 +274,8 @@ Interpreter.prototype.initialize = function(obj) { this.stack = []; this.altstack = []; this.pc = 0; + this.satoshis = 0; + this.sigversion = 0; this.pbegincodehash = 0; this.nOpCount = 0; this.vfExec = []; @@ -173,6 +291,8 @@ Interpreter.prototype.set = function(obj) { this.altstack = obj.altack || this.altstack; this.pc = typeof obj.pc !== 'undefined' ? obj.pc : this.pc; this.pbegincodehash = typeof obj.pbegincodehash !== 'undefined' ? obj.pbegincodehash : this.pbegincodehash; + this.sigversion = typeof obj.sigversion !== 'undefined' ? obj.sigversion : this.sigversion; + this.satoshis = typeof obj.satoshis !== 'undefined' ? obj.satoshis : this.satoshis; this.nOpCount = typeof obj.nOpCount !== 'undefined' ? obj.nOpCount : this.nOpCount; this.vfExec = obj.vfExec || this.vfExec; this.errstr = obj.errstr || this.errstr; @@ -231,6 +351,8 @@ Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); // CLTV See BIP65 for details. Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9); +Interpreter.SCRIPT_VERIFY_WITNESS = (1 << 10); +Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 11); Interpreter.castToBool = function(buf) { for (var i = 0; i < buf.length; i++) { @@ -1111,7 +1233,7 @@ Interpreter.prototype.step = function() { try { sig = Signature.fromTxFormat(bufSig); pubkey = PublicKey.fromBuffer(bufPubkey, false); - fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript); + fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion); } catch (e) { //invalid sig or pubkey fSuccess = false; @@ -1200,7 +1322,7 @@ Interpreter.prototype.step = function() { try { sig = Signature.fromTxFormat(bufSig); pubkey = PublicKey.fromBuffer(bufPubkey, false); - fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript); + fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion); } catch (e) { //invalid sig or pubkey fOk = false;