/// <summary> /// Execution of script /// </summary> /// <param name="stack"></param> /// <param name="script">Script to execute</param> /// <param name="txTo">Transaction instance</param> /// <param name="nIn">Input number</param> /// <param name="flags">Signature checking flags</param> /// <param name="nHashType">Hash type flag</param> /// <returns></returns> public static bool EvalScript(ref List<byte[]> stack, CScript script, CTransaction txTo, int nIn, int flags, int nHashType) { var scriptBytes = ((byte[])script); if (scriptBytes.Length > 10000) { return false; // Size limit failed } var vfExec = new List<bool>(); int nOpCount = 0; int nCodeHashBegin = 0; var falseBytes = new byte[0]; var trueBytes = new byte[] { 0x01 }; var CodeQueue = script.GetInstructionQueue(); var altStack = new List<byte[]>(); #if !DEBUG try { #endif instruction opcode; byte[] pushArg; while (GetOp(ref CodeQueue, out opcode, out pushArg)) // Read instructions { bool fExec = vfExec.IndexOf(false) == -1; if (pushArg.Length > 520) { return false; // Script element size limit failed } if (opcode > instruction.OP_16 && ++nOpCount > 201) { return false; } if (fExec && 0 <= opcode && opcode <= instruction.OP_PUSHDATA4) { stack.Add(pushArg); // Push argument to stack } else if (fExec || (instruction.OP_IF <= opcode && opcode <= instruction.OP_ENDIF)) switch (opcode) { // // Disabled instructions // case instruction.OP_CAT: case instruction.OP_SUBSTR: case instruction.OP_LEFT: case instruction.OP_RIGHT: case instruction.OP_INVERT: case instruction.OP_AND: case instruction.OP_OR: case instruction.OP_XOR: case instruction.OP_2MUL: case instruction.OP_2DIV: case instruction.OP_MUL: case instruction.OP_DIV: case instruction.OP_MOD: case instruction.OP_LSHIFT: case instruction.OP_RSHIFT: return false; // // Push integer instructions // case instruction.OP_1NEGATE: case instruction.OP_1: case instruction.OP_2: case instruction.OP_3: case instruction.OP_4: case instruction.OP_5: case instruction.OP_6: case instruction.OP_7: case instruction.OP_8: case instruction.OP_9: case instruction.OP_10: case instruction.OP_11: case instruction.OP_12: case instruction.OP_13: case instruction.OP_14: case instruction.OP_15: case instruction.OP_16: { // ( -- value) BigInteger bn = DecodeOP_N(opcode, true); stack.Add(bn.ToByteArray()); } break; // // Extension // case instruction.OP_NOP: case instruction.OP_NOP1: case instruction.OP_NOP2: case instruction.OP_NOP3: case instruction.OP_NOP4: case instruction.OP_NOP5: case instruction.OP_NOP6: case instruction.OP_NOP7: case instruction.OP_NOP8: case instruction.OP_NOP9: case instruction.OP_NOP10: { // Just do nothing } break; // // Control // case instruction.OP_IF: case instruction.OP_NOTIF: { // <expression> if [statements] [else [statements]] endif var fValue = false; if (fExec) { if (stack.Count() < 1) { return false; } var vch = stacktop(ref stack, -1); fValue = CastToBool(vch); if (opcode == instruction.OP_NOTIF) { fValue = !fValue; } popstack(ref stack); } vfExec.Add(fValue); } break; case instruction.OP_ELSE: { int nExecCount = vfExec.Count(); if (nExecCount == 0) { return false; } vfExec[nExecCount - 1] = !vfExec[nExecCount - 1]; } break; case instruction.OP_ENDIF: { int nExecCount = vfExec.Count(); if (nExecCount == 0) { return false; } vfExec.RemoveAt(nExecCount - 1); } break; case instruction.OP_VERIFY: { // (true -- ) or // (false -- false) and return if (stack.Count() < 1) { return false; } bool fValue = CastToBool(stacktop(ref stack, -1)); if (fValue) { popstack(ref stack); } else { return false; } } break; case instruction.OP_RETURN: { return false; } // // Stack ops // case instruction.OP_TOALTSTACK: { if (stack.Count() < 1) { return false; } altStack.Add(stacktop(ref stack, -1)); popstack(ref stack); } break; case instruction.OP_FROMALTSTACK: { if (altStack.Count() < 1) { return false; } stack.Add(stacktop(ref stack, -1)); popstack(ref altStack); } break; case instruction.OP_2DROP: { // (x1 x2 -- ) if (stack.Count() < 2) { return false; } popstack(ref stack); popstack(ref stack); } break; case instruction.OP_2DUP: { // (x1 x2 -- x1 x2 x1 x2) if (stack.Count() < 2) { return false; } var vch1 = stacktop(ref stack, -2); var vch2 = stacktop(ref stack, -1); stack.Add(vch1); stack.Add(vch2); } break; case instruction.OP_3DUP: { // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) if (stack.Count() < 3) { return false; } var vch1 = stacktop(ref stack, -3); var vch2 = stacktop(ref stack, -2); var vch3 = stacktop(ref stack, -1); stack.Add(vch1); stack.Add(vch2); stack.Add(vch3); } break; case instruction.OP_2OVER: { // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) if (stack.Count() < 4) { return false; } var vch1 = stacktop(ref stack, -4); var vch2 = stacktop(ref stack, -3); stack.Add(vch1); stack.Add(vch2); } break; case instruction.OP_2ROT: { int nStackDepth = stack.Count(); // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) if (nStackDepth < 6) { return false; } var vch1 = stacktop(ref stack, -6); var vch2 = stacktop(ref stack, -5); stack.RemoveRange(nStackDepth - 6, 2); stack.Add(vch1); stack.Add(vch2); } break; case instruction.OP_2SWAP: { // (x1 x2 x3 x4 -- x3 x4 x1 x2) int nStackDepth = stack.Count; if (nStackDepth < 4) { return false; } stack.Swap(nStackDepth - 4, nStackDepth - 2); stack.Swap(nStackDepth - 3, nStackDepth - 1); } break; case instruction.OP_IFDUP: { // (x - 0 | x x) if (stack.Count() < 1) { return false; } var vch = stacktop(ref stack, -1); if (CastToBool(vch)) { stack.Add(vch); } } break; case instruction.OP_DEPTH: { // -- stacksize BigInteger bn = new BigInteger((ushort)stack.Count()); stack.Add(bn.ToByteArray()); } break; case instruction.OP_DROP: { // (x -- ) if (stack.Count() < 1) { return false; } popstack(ref stack); } break; case instruction.OP_DUP: { // (x -- x x) if (stack.Count() < 1) { return false; } var vch = stacktop(ref stack, -1); stack.Add(vch); } break; case instruction.OP_NIP: { // (x1 x2 -- x2) int nStackDepth = stack.Count(); if (nStackDepth < 2) { return false; } stack.RemoveAt(nStackDepth - 2); } break; case instruction.OP_OVER: { // (x1 x2 -- x1 x2 x1) if (stack.Count() < 2) { return false; } var vch = stacktop(ref stack, -2); stack.Add(vch); } break; case instruction.OP_PICK: case instruction.OP_ROLL: { // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) int nStackDepth = stack.Count(); if (nStackDepth < 2) { return false; } int n = (int)CastToBigInteger(stacktop(ref stack, -1)); popstack(ref stack); if (n < 0 || n >= stack.Count()) { return false; } var vch = stacktop(ref stack, -n - 1); if (opcode == instruction.OP_ROLL) { stack.RemoveAt(nStackDepth - n - 1); } stack.Add(vch); } break; case instruction.OP_ROT: { // (x1 x2 x3 -- x2 x3 x1) // x2 x1 x3 after first swap // x2 x3 x1 after second swap int nStackDepth = stack.Count(); if (nStackDepth < 3) { return false; } stack.Swap(nStackDepth - 3, nStackDepth - 2); stack.Swap(nStackDepth - 2, nStackDepth - 1); } break; case instruction.OP_SWAP: { // (x1 x2 -- x2 x1) int nStackDepth = stack.Count(); if (nStackDepth < 2) { return false; } stack.Swap(nStackDepth - 2, nStackDepth - 1); } break; case instruction.OP_TUCK: { // (x1 x2 -- x2 x1 x2) int nStackDepth = stack.Count(); if (nStackDepth < 2) { return false; } var vch = stacktop(ref stack, -1); stack.Insert(nStackDepth - 2, vch); } break; case instruction.OP_SIZE: { // (in -- in size) if (stack.Count() < 1) { return false; } var bnSize = new BigInteger((ushort)stacktop(ref stack, -1).Count()); stack.Add(bnSize.ToByteArray()); } break; // // Bitwise logic // case instruction.OP_EQUAL: case instruction.OP_EQUALVERIFY: //case instruction.OP_NOTEQUAL: // use OP_NUMNOTEQUAL { // (x1 x2 - bool) if (stack.Count() < 2) { return false; } var vch1 = stacktop(ref stack, -2); var vch2 = stacktop(ref stack, -1); bool fEqual = (vch1.SequenceEqual(vch2)); // 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 == instruction.OP_NOTEQUAL) // fEqual = !fEqual; popstack(ref stack); popstack(ref stack); stack.Add(fEqual ? trueBytes : falseBytes); if (opcode == instruction.OP_EQUALVERIFY) { if (fEqual) { popstack(ref stack); } else { return false; } } } break; // // Numeric // case instruction.OP_1ADD: case instruction.OP_1SUB: case instruction.OP_NEGATE: case instruction.OP_ABS: case instruction.OP_NOT: case instruction.OP_0NOTEQUAL: { // (in -- out) if (stack.Count() < 1) { return false; } var bn = CastToBigInteger(stacktop(ref stack, -1)); switch (opcode) { case instruction.OP_1ADD: bn = bn + 1; break; case instruction.OP_1SUB: bn = bn - 1; break; case instruction.OP_NEGATE: bn = -bn; break; case instruction.OP_ABS: bn = BigInteger.Abs(bn); break; case instruction.OP_NOT: bn = bn == 0 ? 1 : 0; break; case instruction.OP_0NOTEQUAL: bn = bn != 0 ? 1 : 0; break; } popstack(ref stack); stack.Add(bn.ToByteArray()); } break; case instruction.OP_ADD: case instruction.OP_SUB: case instruction.OP_BOOLAND: case instruction.OP_BOOLOR: case instruction.OP_NUMEQUAL: case instruction.OP_NUMEQUALVERIFY: case instruction.OP_NUMNOTEQUAL: case instruction.OP_LESSTHAN: case instruction.OP_GREATERTHAN: case instruction.OP_LESSTHANOREQUAL: case instruction.OP_GREATERTHANOREQUAL: case instruction.OP_MIN: case instruction.OP_MAX: { // (x1 x2 -- out) if (stack.Count() < 2) { return false; } var bn1 = CastToBigInteger(stacktop(ref stack, -2)); var bn2 = CastToBigInteger(stacktop(ref stack, -1)); BigInteger bn = 0; switch (opcode) { case instruction.OP_ADD: bn = bn1 + bn2; break; case instruction.OP_SUB: bn = bn1 - bn2; break; case instruction.OP_BOOLAND: bn = (bn1 != 0 && bn2 != 0) ? 1 : 0; break; case instruction.OP_BOOLOR: bn = (bn1 != 0 || bn2 != 0) ? 1 : 0; break; case instruction.OP_NUMEQUAL: bn = (bn1 == bn2) ? 1 : 0; break; case instruction.OP_NUMEQUALVERIFY: bn = (bn1 == bn2) ? 1 : 0; break; case instruction.OP_NUMNOTEQUAL: bn = (bn1 != bn2) ? 1 : 0; break; case instruction.OP_LESSTHAN: bn = (bn1 < bn2) ? 1 : 0; break; case instruction.OP_GREATERTHAN: bn = (bn1 > bn2) ? 1 : 0; break; case instruction.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2) ? 1 : 0; break; case instruction.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2) ? 1 : 0; break; case instruction.OP_MIN: bn = (bn1 < bn2 ? bn1 : bn2); break; case instruction.OP_MAX: bn = (bn1 > bn2 ? bn1 : bn2); break; } popstack(ref stack); popstack(ref stack); stack.Add(bn.ToByteArray()); if (opcode == instruction.OP_NUMEQUALVERIFY) { if (CastToBool(stacktop(ref stack, -1))) { popstack(ref stack); } else { return false; } } } break; case instruction.OP_WITHIN: { // (x min max -- out) if (stack.Count() < 3) { return false; } var bn1 = CastToBigInteger(stacktop(ref stack, -3)); var bn2 = CastToBigInteger(stacktop(ref stack, -2)); var bn3 = CastToBigInteger(stacktop(ref stack, -1)); bool fValue = (bn2 <= bn1 && bn1 < bn3); popstack(ref stack); popstack(ref stack); popstack(ref stack); stack.Add(fValue ? trueBytes : falseBytes); } break; // // Crypto // case instruction.OP_RIPEMD160: case instruction.OP_SHA1: case instruction.OP_SHA256: case instruction.OP_HASH160: case instruction.OP_HASH256: { // (in -- hash) if (stack.Count() < 1) { return false; } byte[] hash = null; var data = stacktop(ref stack, -1); switch (opcode) { case instruction.OP_HASH160: hash = CryptoUtils.ComputeHash160(data); break; case instruction.OP_HASH256: hash = CryptoUtils.ComputeHash256(data); break; case instruction.OP_SHA1: hash = CryptoUtils.ComputeSha1(data); break; case instruction.OP_SHA256: hash = CryptoUtils.ComputeSha256(data); break; case instruction.OP_RIPEMD160: hash = CryptoUtils.ComputeRipeMD160(data); break; } popstack(ref stack); stack.Add(hash); } break; case instruction.OP_CODESEPARATOR: { // Hash starts after the code separator nCodeHashBegin = CodeQueue.Index; } break; case instruction.OP_CHECKSIG: case instruction.OP_CHECKSIGVERIFY: { // (sig pubkey -- bool) if (stack.Count() < 2) { return false; } var sigBytes = stacktop(ref stack, -2); var pubkeyBytes = stacktop(ref stack, -1); // Subset of script starting at the most recent codeseparator var scriptCode = new CScript(scriptBytes.Skip(nCodeHashBegin).ToArray()); // There's no way for a signature to sign itself scriptCode.RemovePattern(sigBytes); bool fSuccess = IsCanonicalSignature(sigBytes, flags) && IsCanonicalPubKey(pubkeyBytes, flags) && CheckSig(sigBytes, pubkeyBytes, scriptCode, txTo, nIn, nHashType, flags); popstack(ref stack); popstack(ref stack); stack.Add(fSuccess ? trueBytes : falseBytes); if (opcode == instruction.OP_CHECKSIGVERIFY) { if (fSuccess) { popstack(ref stack); } else { return false; } } } break; case instruction.OP_CHECKMULTISIG: case instruction.OP_CHECKMULTISIGVERIFY: { // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) int i = 1; if (stack.Count() < i) { return false; } int nKeysCount = (int)CastToBigInteger(stacktop(ref stack, -i)); if (nKeysCount < 0 || nKeysCount > 20) { return false; } nOpCount += nKeysCount; if (nOpCount > 201) { return false; } int ikey = ++i; i += nKeysCount; if (stack.Count() < i) { return false; } int nSigsCount = (int)CastToBigInteger(stacktop(ref stack, -i)); if (nSigsCount < 0 || nSigsCount > nKeysCount) { return false; } int isig = ++i; i += nSigsCount; if (stack.Count() < i) { return false; } // Subset of script starting at the most recent codeseparator var scriptCode = new CScript(scriptBytes.Skip(nCodeHashBegin).ToArray()); // There is no way for a signature to sign itself, so we need to drop the signatures for (int k = 0; k < nSigsCount; k++) { var vchSig = stacktop(ref stack, -isig - k); scriptCode.RemovePattern(vchSig); } bool fSuccess = true; while (fSuccess && nSigsCount > 0) { var sigBytes = stacktop(ref stack, -isig); var pubKeyBytes = stacktop(ref stack, -ikey); // Check signature bool fOk = IsCanonicalSignature(sigBytes, flags) && IsCanonicalPubKey(pubKeyBytes, flags) && CheckSig(sigBytes, pubKeyBytes, scriptCode, txTo, nIn, nHashType, flags); if (fOk) { isig++; nSigsCount--; } ikey++; nKeysCount--; // If there are more signatures left than keys left, // then too many signatures have failed if (nSigsCount > nKeysCount) { fSuccess = false; } } while (i-- > 1) { popstack(ref stack); } // A bug causes CHECKMULTISIG to consume one extra argument // whose contents were not checked in any way. // // Unfortunately this is a potential source of mutability, // so optionally verify it is exactly equal to zero prior // to removing it from the stack. if (stack.Count() < 1) { return false; } if ((flags & (int)scriptflag.SCRIPT_VERIFY_NULLDUMMY) != 0 && stacktop(ref stack, -1).Count() != 0) { return false; // CHECKMULTISIG dummy argument not null } popstack(ref stack); stack.Add(fSuccess ? trueBytes : falseBytes); if (opcode == instruction.OP_CHECKMULTISIGVERIFY) { if (fSuccess) { popstack(ref stack); } else { return false; } } } break; default: return false; } // Size limits if (stack.Count() + altStack.Count() > 1000) { return false; } } #if !DEBUG } catch (Exception) { // If there are any exceptions then just return false. return false; } #endif if (vfExec.Count() != 0) { // Something went wrong with conditional instructions. return false; } return true; }
/// <summary> /// Return public keys or hashes from scriptPubKey, for 'standard' transaction types. /// </summary> /// <param name="scriptPubKey">CScript instance</param> /// <param name="typeRet">Output type</param> /// <param name="solutions">Set of solutions</param> /// <returns>Result</returns> public static bool Solver(CScript scriptPubKey, out txnouttype typeRet, out IList<byte[]> solutions) { byte[] scriptBytes = scriptPubKey; solutions = new List<byte[]>(); // There are shortcuts for pay-to-script-hash and pay-to-pubkey-hash, which are more constrained than the other types. // It is always OP_HASH160 20 [20 byte hash] OP_EQUAL if (scriptPubKey.IsPayToScriptHash) { typeRet = txnouttype.TX_SCRIPTHASH; // Take 20 bytes with offset of 2 bytes var hashBytes = scriptBytes.Skip(2).Take(20); solutions.Add(hashBytes.ToArray()); return true; } // It is always OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG if (scriptPubKey.IsPayToPubKeyHash) { typeRet = txnouttype.TX_PUBKEYHASH; // Take 20 bytes with offset of 3 bytes var hashBytes = scriptBytes.Skip(3).Take(20); solutions.Add(hashBytes.ToArray()); return true; } var templateTuples = new List<Tuple<txnouttype, byte[]>>(); // Sender provides pubkey, receiver adds signature // [ECDSA public key] OP_CHECKSIG templateTuples.Add( new Tuple<txnouttype, byte[]>( txnouttype.TX_PUBKEY, new byte[] { (byte)instruction.OP_PUBKEY, (byte)instruction.OP_CHECKSIG }) ); // Sender provides N pubkeys, receivers provides M signatures // N [pubkey1] [pubkey2] ... [pubkeyN] M OP_CHECKMULTISIG // Where N and M are small integer instructions (OP1 ... OP_16) templateTuples.Add( new Tuple<txnouttype, byte[]>( txnouttype.TX_MULTISIG, new byte[] { (byte)instruction.OP_SMALLINTEGER, (byte)instruction.OP_PUBKEYS, (byte)instruction.OP_SMALLINTEGER, (byte)instruction.OP_CHECKMULTISIG }) ); // Data-carrying output // OP_RETURN [up to 80 bytes of data] templateTuples.Add( new Tuple<txnouttype, byte[]>( txnouttype.TX_NULL_DATA, new byte[] { (byte)instruction.OP_RETURN, (byte)instruction.OP_SMALLDATA }) ); // Nonstandard tx output typeRet = txnouttype.TX_NONSTANDARD; foreach (var templateTuple in templateTuples) { var script1 = scriptPubKey; var script2 = new CScript(templateTuple.Item2); instruction opcode1, opcode2; // Compare var bq1 = script1.GetInstructionQueue(); var bq2 = script2.GetInstructionQueue(); byte[] args1, args2; int last1 = ((byte[])script1).Length - 1; int last2 = ((byte[])script2).Length - 1; while (true) { if (bq1.Index == last1 && bq2.Index == last2) { // Found a match typeRet = templateTuple.Item1; if (typeRet == txnouttype.TX_MULTISIG) { // Additional checks for TX_MULTISIG: var m = solutions.First().First(); var n = solutions.Last().First(); if (m < 1 || n < 1 || m > n || solutions.Count - 2 != n) { return false; } } return true; } if (!GetOp(ref bq1, out opcode1, out args1)) { break; } if (!GetOp(ref bq2, out opcode2, out args2)) { break; } // Template matching instructions: if (opcode2 == instruction.OP_PUBKEYS) { while (args1.Count() >= 33 && args1.Count() <= 120) { solutions.Add(args1); if (!GetOp(ref bq1, out opcode1, out args1)) { break; } } if (!GetOp(ref bq2, out opcode2, out args2)) { break; } // Normal situation is to fall through // to other if/else statements } if (opcode2 == instruction.OP_PUBKEY) { int PubKeyLen = args1.Count(); if (PubKeyLen < 33 || PubKeyLen > 120) { break; } solutions.Add(args1); } else if (opcode2 == instruction.OP_PUBKEYHASH) { if (args1.Count() != 20) // hash160 size { break; } solutions.Add(args1); } else if (opcode2 == instruction.OP_SMALLINTEGER) { // Single-byte small integer pushed onto solutions try { var n = (byte)DecodeOP_N(opcode1); solutions.Add(new byte[] { n }); } catch (Exception) { break; } } else if (opcode2 == instruction.OP_SMALLDATA) { // small pushdata, <= 80 bytes if (args1.Length > 80) { break; } } else if (opcode1 != opcode2 || !args1.SequenceEqual(args2)) { // Others must match exactly break; } } } solutions.Clear(); typeRet = txnouttype.TX_NONSTANDARD; return false; }