-\feffusing System;
+\feff/**
+ * Novacoin classes library
+ * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using System;
+using System.Linq;
using System.Text;
-
-using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics.Contracts;
namespace Novacoin
{
- /** Script opcodes */
- public enum opcodetype
- {
- // push value
- OP_0 = 0x00,
- OP_FALSE = OP_0,
- OP_PUSHDATA1 = 0x4c,
- OP_PUSHDATA2 = 0x4d,
- OP_PUSHDATA4 = 0x4e,
- OP_1NEGATE = 0x4f,
- OP_RESERVED = 0x50,
- OP_1 = 0x51,
- OP_TRUE=OP_1,
- OP_2 = 0x52,
- OP_3 = 0x53,
- OP_4 = 0x54,
- OP_5 = 0x55,
- OP_6 = 0x56,
- OP_7 = 0x57,
- OP_8 = 0x58,
- OP_9 = 0x59,
- OP_10 = 0x5a,
- OP_11 = 0x5b,
- OP_12 = 0x5c,
- OP_13 = 0x5d,
- OP_14 = 0x5e,
- OP_15 = 0x5f,
- OP_16 = 0x60,
-
- // control
- OP_NOP = 0x61,
- OP_VER = 0x62,
- OP_IF = 0x63,
- OP_NOTIF = 0x64,
- OP_VERIF = 0x65,
- OP_VERNOTIF = 0x66,
- OP_ELSE = 0x67,
- OP_ENDIF = 0x68,
- OP_VERIFY = 0x69,
- OP_RETURN = 0x6a,
-
- // stack ops
- OP_TOALTSTACK = 0x6b,
- OP_FROMALTSTACK = 0x6c,
- OP_2DROP = 0x6d,
- OP_2DUP = 0x6e,
- OP_3DUP = 0x6f,
- OP_2OVER = 0x70,
- OP_2ROT = 0x71,
- OP_2SWAP = 0x72,
- OP_IFDUP = 0x73,
- OP_DEPTH = 0x74,
- OP_DROP = 0x75,
- OP_DUP = 0x76,
- OP_NIP = 0x77,
- OP_OVER = 0x78,
- OP_PICK = 0x79,
- OP_ROLL = 0x7a,
- OP_ROT = 0x7b,
- OP_SWAP = 0x7c,
- OP_TUCK = 0x7d,
-
- // splice ops
- OP_CAT = 0x7e,
- OP_SUBSTR = 0x7f,
- OP_LEFT = 0x80,
- OP_RIGHT = 0x81,
- OP_SIZE = 0x82,
-
- // bit logic
- OP_INVERT = 0x83,
- OP_AND = 0x84,
- OP_OR = 0x85,
- OP_XOR = 0x86,
- OP_EQUAL = 0x87,
- OP_EQUALVERIFY = 0x88,
- OP_RESERVED1 = 0x89,
- OP_RESERVED2 = 0x8a,
-
- // numeric
- OP_1ADD = 0x8b,
- OP_1SUB = 0x8c,
- OP_2MUL = 0x8d,
- OP_2DIV = 0x8e,
- OP_NEGATE = 0x8f,
- OP_ABS = 0x90,
- OP_NOT = 0x91,
- OP_0NOTEQUAL = 0x92,
-
- OP_ADD = 0x93,
- OP_SUB = 0x94,
- OP_MUL = 0x95,
- OP_DIV = 0x96,
- OP_MOD = 0x97,
- OP_LSHIFT = 0x98,
- OP_RSHIFT = 0x99,
-
- OP_BOOLAND = 0x9a,
- OP_BOOLOR = 0x9b,
- OP_NUMEQUAL = 0x9c,
- OP_NUMEQUALVERIFY = 0x9d,
- OP_NUMNOTEQUAL = 0x9e,
- OP_LESSTHAN = 0x9f,
- OP_GREATERTHAN = 0xa0,
- OP_LESSTHANOREQUAL = 0xa1,
- OP_GREATERTHANOREQUAL = 0xa2,
- OP_MIN = 0xa3,
- OP_MAX = 0xa4,
-
- OP_WITHIN = 0xa5,
-
- // crypto
- OP_RIPEMD160 = 0xa6,
- OP_SHA1 = 0xa7,
- OP_SHA256 = 0xa8,
- OP_HASH160 = 0xa9,
- OP_HASH256 = 0xaa,
- OP_CODESEPARATOR = 0xab,
- OP_CHECKSIG = 0xac,
- OP_CHECKSIGVERIFY = 0xad,
- OP_CHECKMULTISIG = 0xae,
- OP_CHECKMULTISIGVERIFY = 0xaf,
-
- // expansion
- OP_NOP1 = 0xb0,
- OP_NOP2 = 0xb1,
- OP_NOP3 = 0xb2,
- OP_NOP4 = 0xb3,
- OP_NOP5 = 0xb4,
- OP_NOP6 = 0xb5,
- OP_NOP7 = 0xb6,
- OP_NOP8 = 0xb7,
- OP_NOP9 = 0xb8,
- OP_NOP10 = 0xb9,
-
- // template matching params
- OP_SMALLDATA = 0xf9,
- OP_SMALLINTEGER = 0xfa,
- OP_PUBKEYS = 0xfb,
- OP_PUBKEYHASH = 0xfd,
- OP_PUBKEY = 0xfe,
-
- OP_INVALIDOPCODE = 0xff,
- };
-
+ /// <summary>
+ /// Representation of script code
+ /// </summary>
public class CScript
{
- private byte[] scriptCode = {};
+ private List<byte> codeBytes;
+ /// <summary>
+ /// Initializes an empty instance of CScript
+ /// </summary>
public CScript ()
{
+ codeBytes = new List<byte>();
}
- private string GetOpName(opcodetype opcode)
- {
- switch (opcode) {
- // push value
- case opcodetype.OP_0:
- return "0";
- case opcodetype.OP_PUSHDATA1:
- return "OP_PUSHDATA1";
- case opcodetype.OP_PUSHDATA2:
- return "OP_PUSHDATA2";
- case opcodetype.OP_PUSHDATA4:
- return "OP_PUSHDATA4";
- case opcodetype.OP_1NEGATE:
- return "-1";
- case opcodetype.OP_RESERVED:
- return "OP_RESERVED";
- case opcodetype.OP_1:
- return "1";
- case opcodetype.OP_2:
- return "2";
- case opcodetype.OP_3:
- return "3";
- case opcodetype.OP_4:
- return "4";
- case opcodetype.OP_5:
- return "5";
- case opcodetype.OP_6:
- return "6";
- case opcodetype.OP_7:
- return "7";
- case opcodetype.OP_8:
- return "8";
- case opcodetype.OP_9:
- return "9";
- case opcodetype.OP_10:
- return "10";
- case opcodetype.OP_11:
- return "11";
- case opcodetype.OP_12:
- return "12";
- case opcodetype.OP_13:
- return "13";
- case opcodetype.OP_14:
- return "14";
- case opcodetype.OP_15:
- return "15";
- case opcodetype.OP_16:
- return "16";
-
- // control
- case opcodetype.OP_NOP:
- return "OP_NOP";
- case opcodetype.OP_VER:
- return "OP_VER";
- case opcodetype.OP_IF:
- return "OP_IF";
- case opcodetype.OP_NOTIF:
- return "OP_NOTIF";
- case opcodetype.OP_VERIF:
- return "OP_VERIF";
- case opcodetype.OP_VERNOTIF:
- return "OP_VERNOTIF";
- case opcodetype.OP_ELSE:
- return "OP_ELSE";
- case opcodetype.OP_ENDIF:
- return "OP_ENDIF";
- case opcodetype.OP_VERIFY:
- return "OP_VERIFY";
- case opcodetype.OP_RETURN:
- return "OP_RETURN";
-
- // stack ops
- case opcodetype.OP_TOALTSTACK:
- return "OP_TOALTSTACK";
- case opcodetype.OP_FROMALTSTACK:
- return "OP_FROMALTSTACK";
- case opcodetype.OP_2DROP:
- return "OP_2DROP";
- case opcodetype.OP_2DUP:
- return "OP_2DUP";
- case opcodetype.OP_3DUP:
- return "OP_3DUP";
- case opcodetype.OP_2OVER:
- return "OP_2OVER";
- case opcodetype.OP_2ROT:
- return "OP_2ROT";
- case opcodetype.OP_2SWAP:
- return "OP_2SWAP";
- case opcodetype.OP_IFDUP:
- return "OP_IFDUP";
- case opcodetype.OP_DEPTH:
- return "OP_DEPTH";
- case opcodetype.OP_DROP:
- return "OP_DROP";
- case opcodetype.OP_DUP:
- return "OP_DUP";
- case opcodetype.OP_NIP:
- return "OP_NIP";
- case opcodetype.OP_OVER:
- return "OP_OVER";
- case opcodetype.OP_PICK:
- return "OP_PICK";
- case opcodetype.OP_ROLL:
- return "OP_ROLL";
- case opcodetype.OP_ROT:
- return "OP_ROT";
- case opcodetype.OP_SWAP:
- return "OP_SWAP";
- case opcodetype.OP_TUCK:
- return "OP_TUCK";
-
- // splice ops
- case opcodetype.OP_CAT:
- return "OP_CAT";
- case opcodetype.OP_SUBSTR:
- return "OP_SUBSTR";
- case opcodetype.OP_LEFT:
- return "OP_LEFT";
- case opcodetype.OP_RIGHT:
- return "OP_RIGHT";
- case opcodetype.OP_SIZE:
- return "OP_SIZE";
-
- // bit logic
- case opcodetype.OP_INVERT:
- return "OP_INVERT";
- case opcodetype.OP_AND:
- return "OP_AND";
- case opcodetype.OP_OR:
- return "OP_OR";
- case opcodetype.OP_XOR:
- return "OP_XOR";
- case opcodetype.OP_EQUAL:
- return "OP_EQUAL";
- case opcodetype.OP_EQUALVERIFY:
- return "OP_EQUALVERIFY";
- case opcodetype.OP_RESERVED1:
- return "OP_RESERVED1";
- case opcodetype.OP_RESERVED2:
- return "OP_RESERVED2";
-
- // numeric
- case opcodetype.OP_1ADD:
- return "OP_1ADD";
- case opcodetype.OP_1SUB:
- return "OP_1SUB";
- case opcodetype.OP_2MUL:
- return "OP_2MUL";
- case opcodetype.OP_2DIV:
- return "OP_2DIV";
- case opcodetype.OP_NEGATE:
- return "OP_NEGATE";
- case opcodetype.OP_ABS:
- return "OP_ABS";
- case opcodetype.OP_NOT:
- return "OP_NOT";
- case opcodetype.OP_0NOTEQUAL:
- return "OP_0NOTEQUAL";
- case opcodetype.OP_ADD:
- return "OP_ADD";
- case opcodetype.OP_SUB:
- return "OP_SUB";
- case opcodetype.OP_MUL:
- return "OP_MUL";
- case opcodetype.OP_DIV:
- return "OP_DIV";
- case opcodetype.OP_MOD:
- return "OP_MOD";
- case opcodetype.OP_LSHIFT:
- return "OP_LSHIFT";
- case opcodetype.OP_RSHIFT:
- return "OP_RSHIFT";
- case opcodetype.OP_BOOLAND:
- return "OP_BOOLAND";
- case opcodetype.OP_BOOLOR:
- return "OP_BOOLOR";
- case opcodetype.OP_NUMEQUAL:
- return "OP_NUMEQUAL";
- case opcodetype.OP_NUMEQUALVERIFY:
- return "OP_NUMEQUALVERIFY";
- case opcodetype.OP_NUMNOTEQUAL:
- return "OP_NUMNOTEQUAL";
- case opcodetype.OP_LESSTHAN:
- return "OP_LESSTHAN";
- case opcodetype.OP_GREATERTHAN:
- return "OP_GREATERTHAN";
- case opcodetype.OP_LESSTHANOREQUAL:
- return "OP_LESSTHANOREQUAL";
- case opcodetype.OP_GREATERTHANOREQUAL:
- return "OP_GREATERTHANOREQUAL";
- case opcodetype.OP_MIN:
- return "OP_MIN";
- case opcodetype.OP_MAX:
- return "OP_MAX";
- case opcodetype.OP_WITHIN:
- return "OP_WITHIN";
-
- // crypto
- case opcodetype.OP_RIPEMD160:
- return "OP_RIPEMD160";
- case opcodetype.OP_SHA1:
- return "OP_SHA1";
- case opcodetype.OP_SHA256:
- return "OP_SHA256";
- case opcodetype.OP_HASH160:
- return "OP_HASH160";
- case opcodetype.OP_HASH256:
- return "OP_HASH256";
- case opcodetype.OP_CODESEPARATOR:
- return "OP_CODESEPARATOR";
- case opcodetype.OP_CHECKSIG:
- return "OP_CHECKSIG";
- case opcodetype.OP_CHECKSIGVERIFY:
- return "OP_CHECKSIGVERIFY";
- case opcodetype.OP_CHECKMULTISIG:
- return "OP_CHECKMULTISIG";
- case opcodetype.OP_CHECKMULTISIGVERIFY:
- return "OP_CHECKMULTISIGVERIFY";
-
- // expanson
- case opcodetype.OP_NOP1:
- return "OP_NOP1";
- case opcodetype.OP_NOP2:
- return "OP_NOP2";
- case opcodetype.OP_NOP3:
- return "OP_NOP3";
- case opcodetype.OP_NOP4:
- return "OP_NOP4";
- case opcodetype.OP_NOP5:
- return "OP_NOP5";
- case opcodetype.OP_NOP6:
- return "OP_NOP6";
- case opcodetype.OP_NOP7:
- return "OP_NOP7";
- case opcodetype.OP_NOP8:
- return "OP_NOP8";
- case opcodetype.OP_NOP9:
- return "OP_NOP9";
- case opcodetype.OP_NOP10:
- return "OP_NOP10";
-
- // template matching params
- case opcodetype.OP_PUBKEYHASH:
- return "OP_PUBKEYHASH";
- case opcodetype.OP_PUBKEY:
- return "OP_PUBKEY";
- case opcodetype.OP_SMALLDATA:
- return "OP_SMALLDATA";
-
- case opcodetype.OP_INVALIDOPCODE:
- return "OP_INVALIDOPCODE";
- default:
- return "OP_UNKNOWN";
- }
- }
-
- public bool GetOp(ref IEnumerator<byte> codeBytes, ref opcodetype opcodeRet, ref byte[] bytesRet)
- {
- opcodeRet = opcodetype.OP_INVALIDOPCODE;
-
- // Initialize local byte list
- List<byte> bytesRetIn = new List<byte> ();
-
- // Read instruction
- if (!codeBytes.MoveNext())
- return false;
- opcodetype opcode = (opcodetype)codeBytes.Current;
-
- // Immediate operand
- if (opcode <= opcodetype.OP_PUSHDATA4)
- {
- int nSize = 0;
- if (opcode < opcodetype.OP_PUSHDATA1) { // False values
- nSize = (int)opcode;
- } else if (opcode == opcodetype.OP_PUSHDATA1) { // One byte size prefix
- if (!codeBytes.MoveNext ())
- return false;
- nSize = codeBytes.Current;
- } else if (opcode == opcodetype.OP_PUSHDATA2) { // Two bytes size prefix
- byte[] SizeBytes = {0,0};
-
- for (int i = 0; i < 2; i++) { // TODO: implement some intelligent solution instead
- if (!codeBytes.MoveNext ())
- return false;
-
- SizeBytes [i] = codeBytes.Current;
- }
-
- Array.Reverse (SizeBytes);
- BitConverter.ToUInt32(SizeBytes, 0);
- } else if (opcode == opcodetype.OP_PUSHDATA4) { // Four bytes size prefix
- byte[] SizeBytes = {0,0,0,0};
-
- for (int i = 0; i < 4; i++) {
- if (!codeBytes.MoveNext ())
- return false;
-
- SizeBytes [i] = codeBytes.Current;
- }
-
- Array.Reverse (SizeBytes);
- nSize = BitConverter.ToInt32 (SizeBytes, 0);
- }
-
- for (int i = 0; i < nSize; i++) {
- if (!codeBytes.MoveNext ())
- return false;
-
- bytesRetIn.Add (codeBytes.Current);
- }
-
- // Return OP_PUSHDATA arguments
- bytesRet = bytesRetIn.ToArray ();
- }
-
- opcodeRet = opcode;
-
- return true;
- }
-
- // Encode/decode small integers:
- static int DecodeOP_N(opcodetype opcode)
- {
- if (opcode == opcodetype.OP_0)
- return 0;
- if(! (opcode >= opcodetype.OP_1 && opcode <= opcodetype.OP_16))
- throw new Exception ("! (opcode >= opcodetype.OP_1 && opcode <= opcodetype.OP_16)");
- return (int)opcode - (int)(opcodetype.OP_1 - 1);
- }
- static opcodetype EncodeOP_N(int n)
- {
- if (!(n >= 0 && n <= 16))
- throw new Exception ("! (n >= 0 && n <= 16)");
- if (n == 0)
- return opcodetype.OP_0;
- return (opcodetype)(opcodetype.OP_1+n-1);
- }
-
+ /// <summary>
+ /// Initializes new instance of CScript and fills it with supplied bytes
+ /// </summary>
+ /// <param name="bytes">Enumerator interface for byte sequence</param>
+ public CScript(byte[] bytes)
+ {
+ codeBytes = new List<byte>(bytes);
+ }
+
+ /// <summary>
+ /// Return a new instance of ByteQueue object for current code bytes
+ /// </summary>
+ /// <returns></returns>
+ public InstructionQueue GetInstructionQueue()
+ {
+ return new InstructionQueue(ref codeBytes);
+ }
+
+ /// <summary>
+ /// Adds specified operation to instruction list
+ /// </summary>
+ /// <param name="opcode"></param>
+ public void AddInstruction(instruction opcode)
+ {
+ Contract.Requires<ArgumentException>(opcode >= instruction.OP_0 && opcode <= instruction.OP_INVALIDOPCODE, "Invalid instruction.");
+
+ codeBytes.Add((byte)opcode);
+ }
+
+ /// <summary>
+ /// Adds hash to instruction list.
+ /// New items are added in this format:
+ /// hash_length_byte hash_bytes
+ /// </summary>
+ /// <param name="hash">Hash160 instance</param>
+ public void AddHash(uint160 hash)
+ {
+ codeBytes.Add((byte)hash.Size);
+ codeBytes.AddRange((byte[])hash);
+ }
+
+ /// <summary>
+ /// Adds hash to instruction list.
+ /// New items are added in this format:
+ /// hash_length_byte hash_bytes
+ /// </summary>
+ /// <param name="hash">Hash256 instance</param>
+ public void AddHash(uint256 hash)
+ {
+ codeBytes.Add((byte)hash.Size);
+ codeBytes.AddRange((byte[])hash);
+ }
+
+ /// <summary>
+ /// Create new OP_PUSHDATAn operator and add it to instruction list
+ /// </summary>
+ /// <param name="dataBytes">Set of data bytes</param>
+ public void PushData(byte[] dataBytes)
+ {
+ var nCount = dataBytes.LongLength;
+
+ if (nCount < (int)instruction.OP_PUSHDATA1)
+ {
+ // OP_0 and OP_FALSE
+ codeBytes.Add((byte)nCount);
+ }
+ else if (nCount < 0xff)
+ {
+ // OP_PUSHDATA1 0x01 [0x5a]
+ codeBytes.Add((byte)instruction.OP_PUSHDATA1);
+ codeBytes.Add((byte)nCount);
+ }
+ else if (nCount < 0xffff)
+ {
+ // OP_PUSHDATA1 0x01 0x00 [0x5a]
+ codeBytes.Add((byte)instruction.OP_PUSHDATA2);
+
+ var szBytes = BitConverter.GetBytes((ushort)nCount);
+ codeBytes.AddRange(szBytes);
+ }
+ else if (nCount < 0xffffffff)
+ {
+ // OP_PUSHDATA1 0x01 0x00 0x00 0x00 [0x5a]
+ codeBytes.Add((byte)instruction.OP_PUSHDATA4);
+
+ var szBytes = BitConverter.GetBytes((uint)nCount);
+ codeBytes.AddRange(szBytes);
+ }
+
+ // Add data bytes
+ codeBytes.AddRange(dataBytes);
+ }
+
+ /// <summary>
+ /// Just insert data array without including any prefixes. Please make sure that you know what you're doing,
+ /// it is recommended to use AddInstruction, AddHash or PushData instead.
+ /// </summary>
+ /// <param name="dataBytes">Data bytes</param>
+ public void AddRawData(byte[] dataBytes)
+ {
+ // Add data bytes
+ codeBytes.AddRange(dataBytes);
+ }
+
+ /// <summary>
+ /// Scan pushed data bytes for pattern and, in case of exact match, remove it.
+ /// </summary>
+ /// <param name="pattern">Pattern sequence</param>
+ /// <returns>Matches count</returns>
+ public int RemovePattern(byte[] pattern)
+ {
+ // There is no sense to continue if pattern is empty or longer than script itself
+ if (pattern.Length == 0 || pattern.Length > codeBytes.Count)
+ {
+ return 0;
+ }
+
+ var count = 0;
+ var bq1 = new InstructionQueue(ref codeBytes);
+
+ byte[] pushData;
+ instruction opcode;
+
+ var newScript = new CScript();
+
+ while (ScriptCode.GetOp(ref bq1, out opcode, out pushData))
+ {
+ if (pushData.Length == 0)
+ {
+ // No data, put instruction on its place
+ newScript.AddInstruction(opcode);
+ }
+ else if (!pushData.SequenceEqual(pattern))
+ {
+ // No match, create push operator
+ newScript.PushData(pushData);
+ }
+ else
+ {
+ count++; // match
+ }
+ }
+
+ if (count > 0)
+ {
+ // Replace current script if any matches were found
+ codeBytes = newScript.codeBytes;
+ }
+
+ return count;
+ }
+
+ /// <summary>
+ /// Scan script for specific instruction and remove it if there are some matches.
+ /// </summary>
+ /// <param name="op">Instruction</param>
+ /// <returns>Matches count</returns>
+ public int RemoveInstruction(instruction op)
+ {
+ byte[] pushData;
+ instruction opcode;
+
+ var count = 0;
+ var newScript = new CScript();
+ var bq1 = new InstructionQueue(ref codeBytes);
+
+ while (ScriptCode.GetOp(ref bq1, out opcode, out pushData))
+ {
+ if (pushData.Length != 0 && op != opcode)
+ {
+ // If instruction didn't match then push its data again
+ newScript.PushData(pushData);
+ }
+ else if (Enum.IsDefined(typeof(instruction), op) && op != opcode)
+ {
+ // Instruction didn't match
+ newScript.AddInstruction(opcode);
+ }
+ else
+ {
+ count++; // match
+ }
+ }
+
+ if (count > 0)
+ {
+ // Replace current script if any matches were found
+ codeBytes = newScript.codeBytes;
+ }
+
+ return count;
+ }
+
+ /// <summary>
+ /// Is it true that script doesn't contain anything except push value operations?
+ /// </summary>
+ public bool IsPushOnly
+ {
+ get
+ {
+ var wCodeBytes = new InstructionQueue(ref codeBytes);
+
+ instruction opcode; // Current instruction
+ byte[] pushArgs; // OP_PUSHDATAn argument
+
+ // Scan instructions sequence
+ while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+ {
+ if (opcode > instruction.OP_16)
+ {
+ // We don't allow control instructions here
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Is it true that script doesn't contain non-canonical push operations?
+ /// </summary>
+ public bool HasOnlyCanonicalPushes
+ {
+ get
+ {
+ var wCodeBytes = new InstructionQueue(ref codeBytes);
+
+ byte[] pushArgs; // OP_PUSHDATAn argument
+ instruction opcode; // Current instruction
+
+ // Scan instructions sequence
+ while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+ {
+ var data = pushArgs;
+
+ if (opcode < instruction.OP_PUSHDATA1 && opcode > instruction.OP_0 && (data.Length == 1 && data[0] <= 16))
+ {
+ // Could have used an OP_n code, rather than a 1-byte push.
+ return false;
+ }
+ if (opcode == instruction.OP_PUSHDATA1 && data.Length < (int)instruction.OP_PUSHDATA1)
+ {
+ // Could have used a normal n-byte push, rather than OP_PUSHDATA1.
+ return false;
+ }
+ if (opcode == instruction.OP_PUSHDATA2 && data.Length <= 0xFF)
+ {
+ // Could have used an OP_PUSHDATA1.
+ return false;
+ }
+ if (opcode == instruction.OP_PUSHDATA4 && data.LongLength <= 0xFFFF)
+ {
+ // Could have used an OP_PUSHDATA2.
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Quick test for pay-to-script-hash CScripts
+ /// </summary>
+ public bool IsPayToScriptHash
+ {
+ get
+ {
+ // Sender provides redeem script hash, receiver provides signature list and redeem script
+ // OP_HASH160 20 [20 byte hash] OP_EQUAL
+ return (codeBytes.Count() == 23 &&
+ codeBytes[0] == (byte)instruction.OP_HASH160 &&
+ codeBytes[1] == 0x14 && // 20 bytes hash length prefix
+ codeBytes[22] == (byte)instruction.OP_EQUAL);
+ }
+ }
+
+ /// <summary>
+ /// Quick test for pay-to-pubkeyhash CScripts
+ /// </summary>
+ public bool IsPayToPubKeyHash
+ {
+ get
+ {
+ // Sender provides hash of pubkey, receiver provides signature and pubkey
+ // OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG
+ return (codeBytes.Count == 25 &&
+ codeBytes[0] == (byte)instruction.OP_DUP &&
+ codeBytes[1] == (byte)instruction.OP_HASH160 &&
+ codeBytes[2] == 0x14 && // 20 bytes hash length prefix
+ codeBytes[23] == (byte)instruction.OP_EQUALVERIFY &&
+ codeBytes[24] == (byte)instruction.OP_CHECKSIG);
+ }
+ }
+
+ /// <summary>
+ /// Quick test for Null destination
+ /// </summary>
+ public bool IsNull
+ {
+ get { return codeBytes.Count == 0; }
+ }
+
+ /// <summary>
+ /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
+ /// as 20 sigops. With pay-to-script-hash, that changed:
+ /// CHECKMULTISIGs serialized in scriptSigs are
+ /// counted more accurately, assuming they are of the form
+ /// ... OP_N CHECKMULTISIG ...
+ /// </summary>
+ /// <param name="fAccurate">Legacy mode flag</param>
+ /// <returns>Amount of sigops</returns>
+ public uint GetSigOpCount(bool fAccurate)
+ {
+ var wCodeBytes = new InstructionQueue(ref codeBytes);
+
+ instruction opcode; // Current instruction
+ byte[] pushArgs; // OP_PUSHDATAn argument
+
+ uint nCount = 0;
+ var lastOpcode = instruction.OP_INVALIDOPCODE;
+
+ // Scan instructions sequence
+ while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+ {
+ if (opcode == instruction.OP_CHECKSIG || opcode == instruction.OP_CHECKSIGVERIFY)
+ {
+ nCount++;
+ }
+ else if (opcode == instruction.OP_CHECKMULTISIG || opcode == instruction.OP_CHECKMULTISIGVERIFY)
+ {
+ if (fAccurate && lastOpcode >= instruction.OP_1 && lastOpcode <= instruction.OP_16)
+ {
+ nCount += (uint)ScriptCode.DecodeOP_N(lastOpcode);
+ }
+ else
+ {
+ nCount += 20;
+ }
+ }
+ }
+
+ return nCount;
+ }
+
+ /// <summary>
+ /// Accurately count sigOps, including sigOps in
+ /// pay-to-script-hash transactions
+ /// </summary>
+ /// <param name="scriptSig">pay-to-script-hash scriptPubKey</param>
+ /// <returns>SigOps count</returns>
+ public uint GetSigOpCount(CScript scriptSig)
+ {
+ if (!IsPayToScriptHash)
+ {
+ return GetSigOpCount(true);
+ }
+
+ // This is a pay-to-script-hash scriptPubKey;
+ // get the last item that the scriptSig
+ // pushes onto the stack:
+ InstructionQueue wScriptSig = scriptSig.GetInstructionQueue();
+ int nScriptSigSize = scriptSig.Size;
+
+ instruction opcode; // Current instruction
+ byte[] pushArgs = new byte[0]; // OP_PUSHDATAn argument
+
+
+ while (wScriptSig.Index < nScriptSigSize)
+ {
+ if (!ScriptCode.GetOp(ref wScriptSig, out opcode, out pushArgs))
+ {
+ return 0;
+ }
+
+ if (opcode > instruction.OP_16)
+ {
+ return 0;
+ }
+ }
+
+ /// ... and return its opcount:
+ var subScript = new CScript(pushArgs);
+
+ return subScript.GetSigOpCount(true);
+
+ }
+
+ /// <summary>
+ /// Set pay-to-pubkey destination.
+ /// </summary>
+ /// <param name="pubKey">Instance of CPubKey.</param>
+ public void SetDestination(CPubKey pubKey)
+ {
+ codeBytes.Clear();
+ PushData(pubKey);
+ AddInstruction(instruction.OP_CHECKSIG);
+ }
+
+ /// <summary>
+ /// Set pay-to-pubkeyhash destination
+ /// </summary>
+ /// <param name="ID">Public key hash</param>
+ public void SetDestination(CKeyID ID)
+ {
+ codeBytes.Clear();
+ AddInstruction(instruction.OP_DUP);
+ AddInstruction(instruction.OP_HASH160);
+ AddHash(ID);
+ AddInstruction(instruction.OP_EQUALVERIFY);
+ AddInstruction(instruction.OP_CHECKSIG);
+ }
+
+ /// <summary>
+ /// Set pay-to-scripthash destination
+ /// </summary>
+ /// <param name="ID">Script hash</param>
+ public void SetDestination(CScriptID ID)
+ {
+ codeBytes.Clear();
+ AddInstruction(instruction.OP_HASH160);
+ AddHash(ID);
+ AddInstruction(instruction.OP_EQUAL);
+ }
+
+ /// <summary>
+ /// Reset script code buffer.
+ /// </summary>
+ public void SetNullDestination()
+ {
+ codeBytes.Clear();
+ }
+
+ /// <summary>
+ /// Set multisig destination.
+ /// </summary>
+ /// <param name="nRequired">Amount of required signatures.</param>
+ /// <param name="keys">Set of public keys.</param>
+ public void SetMultiSig(int nRequired, CPubKey[] keys)
+ {
+ codeBytes.Clear();
+ AddInstruction(ScriptCode.EncodeOP_N(nRequired));
+
+ foreach (var key in keys)
+ {
+ PushData(key);
+ }
+
+ AddInstruction(ScriptCode.EncodeOP_N(keys.Length));
+ AddInstruction(instruction.OP_CHECKMULTISIG);
+ }
+
+ /// <summary>
+ /// Access to script code.
+ /// </summary>
+ public static implicit operator byte[] (CScript script)
+ {
+ return script.codeBytes.ToArray();
+ }
+
+ /// <summary>
+ /// Script size
+ /// </summary>
+ public int Size
+ {
+ get { return codeBytes.Count; }
+ }
+
+ public CScriptID ScriptID
+ {
+ get { return new CScriptID(Hash160.Compute160(codeBytes.ToArray())); }
+ }
+
+ /// <summary>
+ /// Disassemble current script code
+ /// </summary>
+ /// <returns>Code listing</returns>
public override string ToString()
{
- // TODO: disassembly
-
- StringBuilder sb = new StringBuilder(scriptCode.Length * 2);
- foreach (byte b in scriptCode)
- {
- sb.AppendFormat ("{0:x2}", b);
- }
- return sb.ToString();
+ var sb = new StringBuilder();
+ var wCodeBytes = new InstructionQueue(ref codeBytes);
+
+ instruction opcode; // Current instruction
+ byte[] pushArgs; // OP_PUSHDATAn argument
+ while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+ {
+ if (sb.Length != 0)
+ {
+ sb.Append(" ");
+ }
+
+ if (0 <= opcode && opcode <= instruction.OP_PUSHDATA4)
+ {
+ sb.Append(ScriptCode.ValueString(pushArgs));
+ }
+ else
+ {
+ sb.Append(ScriptCode.GetOpName(opcode));
+ }
+ }
+
+ return sb.ToString();
}
}
}
-