using System; using System.Linq; using System.Text; using System.Collections.Generic; namespace Novacoin { public class CScriptException : Exception { public CScriptException() { } public CScriptException(string message) : base(message) { } public CScriptException(string message, Exception inner) : base(message, inner) { } } /// /// Representation of script code /// public class CScript { private List codeBytes; /// /// Initializes an empty instance of CScript /// public CScript () { codeBytes = new List(); } /// /// Initializes new instance of CScript and fills it with supplied bytes /// /// Enumerator interface for byte sequence public CScript(IEnumerable bytes) { codeBytes = new List(bytes); } /// /// Return a new instance of ByteQueue object for current code bytes /// /// public ByteQueue GetByteQUeue() { return new ByteQueue(codeBytes); } /// /// Adds specified operation to opcode bytes list /// /// public void AddOp(instruction opcode) { if (opcode < instruction.OP_0 || opcode > instruction.OP_INVALIDOPCODE) { throw new CScriptException("CScript::AddOp() : invalid opcode"); } codeBytes.Add((byte)opcode); } /// /// Adds hash to opcode bytes list. /// New items are added in this format: /// hash_length_byte hash_bytes /// /// Hash160 instance public void AddHash(Hash160 hash) { codeBytes.Add((byte)hash.hashSize); codeBytes.AddRange(hash.hashBytes); } /// /// Adds hash to opcode bytes list. /// New items are added in this format: /// hash_length_byte hash_bytes /// /// Hash256 instance public void AddHash(Hash256 hash) { codeBytes.Add((byte)hash.hashSize); codeBytes.AddRange(hash.hashBytes); } /// /// Create new OP_PUSHDATAn operator and add it to opcode bytes list /// /// Set of data bytes public void PushData(IEnumerable dataBytes) { long nCount = dataBytes.LongCount(); 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 0x00 0x01 [0x5a] codeBytes.Add((byte)instruction.OP_PUSHDATA2); byte[] szBytes = Interop.BEBytes((ushort)nCount); codeBytes.AddRange(szBytes); } else if (nCount < 0xffffffff) { // OP_PUSHDATA1 0x00 0x00 0x00 0x01 [0x5a] codeBytes.Add((byte)instruction.OP_PUSHDATA4); byte[] szBytes = Interop.BEBytes((uint)nCount); codeBytes.AddRange(szBytes); } // Add data bytes codeBytes.AddRange(dataBytes); } /// /// Scan code bytes for pattern /// /// Pattern sequence /// Matches enumerator private IEnumerable FindPattern(IList pattern) { for (int i = 0; i < codeBytes.Count; i++) { if (codeBytes.Skip(i).Take(pattern.Count).SequenceEqual(pattern)) { yield return i; } } } /// /// Scan code bytes for pattern and remove it /// /// Pattern sequence /// Matches number public int RemovePattern(IList pattern) { List resultBytes = new List(codeBytes); int count = 0; int patternLen = pattern.Count; foreach (int i in FindPattern(pattern)) { resultBytes.RemoveRange(i - count * patternLen, patternLen); count++; } codeBytes = resultBytes; return count; } /// /// Is it true that script doesn't contain anything except push value operations? /// public bool IsPushonly { get { ByteQueue wCodeBytes = new ByteQueue(codeBytes); instruction opcode; // Current opcode IEnumerable pushArgs; // OP_PUSHDATAn argument // Scan opcodes sequence while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs)) { if (opcode > instruction.OP_16) { // We don't allow control opcodes here return false; } } return true; } } /// /// Is it true that script doesn't contain non-canonical push operations? /// public bool HasOnlyCanonicalPushes { get { ByteQueue wCodeBytes = new ByteQueue(codeBytes); instruction opcode; // Current opcode IEnumerable pushArgs; // OP_PUSHDATAn argument // Scan opcodes sequence while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs)) { byte[] data = pushArgs.ToArray(); 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 int GetSigOpCount(bool fAccurate) { ByteQueue wCodeBytes = new ByteQueue(codeBytes); instruction opcode; // Current opcode IEnumerable pushArgs; // OP_PUSHDATAn argument int nCount = 0; instruction lastOpcode = instruction.OP_INVALIDOPCODE; // Scan opcodes 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 += 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 int 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(); instruction opcode; // Current opcode IEnumerable pushArgs; // OP_PUSHDATAn argument while (ScriptCode.GetOp(ref wScriptSig, out opcode, out pushArgs)) { if (opcode > instruction.OP_16) { return 0; } } /// ... and return its opcount: CScript 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.PublicBytes); AddOp(instruction.OP_CHECKSIG); } /// /// Set pay-to-pubkeyhash destination /// /// Public key hash public void SetDestination(CKeyID ID) { codeBytes.Clear(); AddOp(instruction.OP_DUP); AddOp(instruction.OP_HASH160); AddHash(ID); AddOp(instruction.OP_EQUALVERIFY); AddOp(instruction.OP_CHECKSIG); } /// /// Set pay-to-scripthash destination /// /// Script hash public void SetDestination(CScriptID ID) { codeBytes.Clear(); AddOp(instruction.OP_HASH160); AddHash(ID); AddOp(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, IEnumerable keys) { codeBytes.Clear(); AddOp(ScriptCode.EncodeOP_N(nRequired)); foreach (CPubKey key in keys) { PushData(key.PublicBytes.ToList()); } AddOp(ScriptCode.EncodeOP_N(keys.Count())); AddOp(instruction.OP_CHECKMULTISIG); } /// /// Access to script code. /// public IEnumerable Bytes { get { return codeBytes; } } public CScriptID ScriptID { get { return new CScriptID(Hash160.Compute160(codeBytes)); } } /// /// Disassemble current script code /// /// Code listing public override string ToString() { StringBuilder sb = new StringBuilder(); ByteQueue wCodeBytes = new ByteQueue(codeBytes); instruction opcode; // Current opcode IEnumerable 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(); } } }