X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCScript.cs;h=229b60b8284554c5b370eefb2f3bc15fbf650eb1;hb=be9d844557911f95165d2c9875c4f5b2822cfc92;hp=9ada832fe99293a3d1586a0c94b7a7c832fc4039;hpb=5cfd34a70fadc9929ef8514b39aaa06797a18503;p=NovacoinLibrary.git diff --git a/Novacoin/CScript.cs b/Novacoin/CScript.cs index 9ada832..229b60b 100644 --- a/Novacoin/CScript.cs +++ b/Novacoin/CScript.cs @@ -1,28 +1,29 @@ -using System; -using System.Text; +/** + * Novacoin classes library + * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com) -using System.Collections; -using System.Collections.Generic; + * 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. -namespace Novacoin -{ - public class CScriptException : Exception - { - public CScriptException() - { - } + * 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. - public CScriptException(string message) - : base(message) - { - } + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ - public CScriptException(string message, Exception inner) - : base(message, inner) - { - } - } +using System; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +namespace Novacoin +{ /// /// Representation of script code /// @@ -41,37 +42,34 @@ namespace Novacoin /// /// Initializes new instance of CScript and fills it with supplied bytes /// - /// List of bytes - public CScript(IList bytes) + /// Enumerator interface for byte sequence + public CScript(byte[] bytes) { codeBytes = new List(bytes); } /// - /// Initializes new instance of CScript and fills it with supplied bytes + /// Return a new instance of ByteQueue object for current code bytes /// - /// Array of bytes - public CScript(byte[] bytes) + /// + public ByteQueue GetByteQueue() { - codeBytes = new List(bytes); + return new ByteQueue(ref codeBytes); } /// - /// Adds specified operation to opcode bytes list + /// Adds specified operation to instruction list /// /// - public void AddOp(opcodetype opcode) + public void AddInstruction(instruction opcode) { - if (opcode < opcodetype.OP_0 || opcode > opcodetype.OP_INVALIDOPCODE) - { - throw new CScriptException("CScript::AddOp() : invalid opcode"); - } + Contract.Requires(opcode >= instruction.OP_0 && opcode <= instruction.OP_INVALIDOPCODE, "Invalid instruction."); codeBytes.Add((byte)opcode); } /// - /// Adds hash to opcode bytes list. + /// Adds hash to instruction list. /// New items are added in this format: /// hash_length_byte hash_bytes /// @@ -79,11 +77,11 @@ namespace Novacoin public void AddHash(Hash160 hash) { codeBytes.Add((byte)hash.hashSize); - codeBytes.AddRange(hash.hashBytes); + codeBytes.AddRange((byte[])hash); } /// - /// Adds hash to opcode bytes list. + /// Adds hash to instruction list. /// New items are added in this format: /// hash_length_byte hash_bytes /// @@ -91,75 +89,455 @@ namespace Novacoin public void AddHash(Hash256 hash) { codeBytes.Add((byte)hash.hashSize); - codeBytes.AddRange(hash.hashBytes); + codeBytes.AddRange((byte[])hash); } - public void PushData(IList dataBytes) + /// + /// Create new OP_PUSHDATAn operator and add it to instruction list + /// + /// Set of data bytes + public void PushData(byte[] dataBytes) { - if (dataBytes.Count < (int)opcodetype.OP_PUSHDATA1) + var nCount = dataBytes.LongLength; + + if (nCount < (int)instruction.OP_PUSHDATA1) { - codeBytes.Add((byte)dataBytes.Count); + // OP_0 and OP_FALSE + codeBytes.Add((byte)nCount); } - else if (dataBytes.Count < 0xff) + else if (nCount < 0xff) { - codeBytes.Add((byte)opcodetype.OP_PUSHDATA1); - codeBytes.Add((byte)dataBytes.Count); + // OP_PUSHDATA1 0x01 [0x5a] + codeBytes.Add((byte)instruction.OP_PUSHDATA1); + codeBytes.Add((byte)nCount); } - else if (dataBytes.Count < 0xffff) + else if (nCount < 0xffff) { - codeBytes.Add((byte)opcodetype.OP_PUSHDATA2); - - byte[] szBytes = BitConverter.GetBytes((short)dataBytes.Count); - if (BitConverter.IsLittleEndian) - Array.Reverse(szBytes); + // OP_PUSHDATA1 0x01 0x00 [0x5a] + codeBytes.Add((byte)instruction.OP_PUSHDATA2); + var szBytes = BitConverter.GetBytes((ushort)nCount); codeBytes.AddRange(szBytes); } - else if ((uint)dataBytes.Count < 0xffffffff) + else if (nCount < 0xffffffff) { - codeBytes.Add((byte)opcodetype.OP_PUSHDATA2); - - byte[] szBytes = BitConverter.GetBytes((uint)dataBytes.Count); - if (BitConverter.IsLittleEndian) - Array.Reverse(szBytes); + // 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); } /// + /// 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. + /// + /// Data bytes + public void AddRawData(byte[] dataBytes) + { + // Add data bytes + codeBytes.AddRange(dataBytes); + } + + /// + /// Scan pushed data bytes for pattern and, in case of exact match, remove it. + /// + /// Pattern sequence + /// Matches count + 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 ByteQueue(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; + } + + /// + /// Scan script for specific instruction and remove it if there are some matches. + /// + /// Instruction + /// Matches count + public int RemoveInstruction(instruction op) + { + byte[] pushData; + instruction opcode; + + var count = 0; + var newScript = new CScript(); + var bq1 = new ByteQueue(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; + } + + /// + /// Is it true that script doesn't contain anything except push value operations? + /// + public bool IsPushOnly + { + get + { + var wCodeBytes = new ByteQueue(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; + } + } + + /// + /// Is it true that script doesn't contain non-canonical push operations? + /// + public bool HasOnlyCanonicalPushes + { + get + { + var wCodeBytes = new ByteQueue(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; + } + } + + /// + /// Quick test for pay-to-script-hash CScripts + /// + 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); + } + } + + /// + /// Quick test for pay-to-pubkeyhash CScripts + /// + 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); + } + } + + /// + /// Quick test for Null destination + /// + public bool IsNull + { + get { return codeBytes.Count == 0; } + } + + /// + /// 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 ... + /// + /// Legacy mode flag + /// Amount of sigops + public uint GetSigOpCount(bool fAccurate) + { + var wCodeBytes = new ByteQueue(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; + } + + /// + /// Accurately count sigOps, including sigOps in + /// pay-to-script-hash transactions + /// + /// pay-to-script-hash scriptPubKey + /// SigOps count + 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: + ByteQueue wScriptSig = scriptSig.GetByteQueue(); + 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); + + } + + /// + /// Set pay-to-pubkey destination. + /// + /// Instance of CPubKey. + public void SetDestination(CPubKey pubKey) + { + codeBytes.Clear(); + PushData(pubKey); + AddInstruction(instruction.OP_CHECKSIG); + } + + /// + /// Set pay-to-pubkeyhash destination + /// + /// Public key hash + 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); + } + + /// + /// Set pay-to-scripthash destination + /// + /// Script hash + public void SetDestination(CScriptID ID) + { + codeBytes.Clear(); + AddInstruction(instruction.OP_HASH160); + AddHash(ID); + AddInstruction(instruction.OP_EQUAL); + } + + /// + /// Reset script code buffer. + /// + public void SetNullDestination() + { + codeBytes.Clear(); + } + + /// + /// Set multisig destination. + /// + /// Amount of required signatures. + /// Set of public keys. + 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); + } + + /// + /// Access to script code. + /// + public static implicit operator byte[] (CScript script) + { + return script.codeBytes.ToArray(); + } + + /// + /// Script size + /// + public int Size + { + get { return codeBytes.Count; } + } + + public CScriptID ScriptID + { + get { return new CScriptID(Hash160.Compute160(codeBytes.ToArray())); } + } + + /// /// Disassemble current script code /// /// Code listing public override string ToString() { - StringBuilder sb = new StringBuilder(); - - WrappedList wCodeBytes = new WrappedList(codeBytes); + var sb = new StringBuilder(); + var wCodeBytes = new ByteQueue(ref codeBytes); - while (wCodeBytes.ItemsLeft > 0) + instruction opcode; // Current instruction + byte[] pushArgs; // OP_PUSHDATAn argument + while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs)) { if (sb.Length != 0) { sb.Append(" "); } - opcodetype opcode; - IEnumerable pushArgs; - if (!ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs)) - { - sb.Append("[error]"); - break; - } - - if (0 <= opcode && opcode <= opcodetype.OP_PUSHDATA4) + if (0 <= opcode && opcode <= instruction.OP_PUSHDATA4) { - sb.Append(ScriptOpcode.ValueString(pushArgs)); + sb.Append(ScriptCode.ValueString(pushArgs)); } else { - sb.Append(ScriptOpcode.GetOpName(opcode)); + sb.Append(ScriptCode.GetOpName(opcode)); } } @@ -167,4 +545,3 @@ namespace Novacoin } } } -