565fda9292e3eafcdeaa1f39c16538a71474a5df
[NovacoinLibrary.git] / Novacoin / CScript.cs
1 \feffusing System;
2 using System.Linq;
3 using System.Text;
4
5 using System.Collections;
6 using System.Collections.Generic;
7
8 namespace Novacoin
9 {
10     public class CScriptException : Exception
11     {
12         public CScriptException()
13         {
14         }
15
16         public CScriptException(string message)
17             : base(message)
18         {
19         }
20
21         public CScriptException(string message, Exception inner)
22             : base(message, inner)
23         {
24         }
25     }
26
27     /// <summary>
28     /// Representation of script code
29     /// </summary>
30         public class CScript
31         {
32         private List<byte> codeBytes;
33
34         /// <summary>
35         /// Initializes an empty instance of CScript
36         /// </summary>
37                 public CScript ()
38                 {
39             codeBytes = new List<byte>();
40                 }
41
42         /// <summary>
43         /// Initializes new instance of CScript and fills it with supplied bytes
44         /// </summary>
45         /// <param name="bytes">Enumerator interface for byte sequence</param>
46         public CScript(IEnumerable<byte> bytes)
47         {
48             codeBytes = new List<byte>(bytes);
49         }
50
51         /// <summary>
52         /// Return a new instance of WrappedList object for current code bytes
53         /// </summary>
54         /// <returns></returns>
55         public WrappedList<byte> GetWrappedList()
56         {
57              return new WrappedList<byte>(codeBytes);
58         }
59
60         /// <summary>
61         /// Adds specified operation to opcode bytes list
62         /// </summary>
63         /// <param name="opcode"></param>
64         public void AddOp(opcodetype opcode)
65         {
66             if (opcode < opcodetype.OP_0 || opcode > opcodetype.OP_INVALIDOPCODE)
67             {
68                 throw new CScriptException("CScript::AddOp() : invalid opcode");
69             }
70
71             codeBytes.Add((byte)opcode);
72         }
73
74         /// <summary>
75         /// Adds hash to opcode bytes list.
76         ///    New items are added in this format:
77         ///    hash_length_byte hash_bytes
78         /// </summary>
79         /// <param name="hash">Hash160 instance</param>
80         public void AddHash(Hash160 hash)
81         {
82             codeBytes.Add((byte)hash.hashSize);
83             codeBytes.AddRange(hash.hashBytes);
84         }
85
86         /// <summary>
87         /// Adds hash to opcode bytes list.
88         ///    New items are added in this format:
89         ///    hash_length_byte hash_bytes
90         /// </summary>
91         /// <param name="hash">Hash256 instance</param>
92         public void AddHash(Hash256 hash)
93         {
94             codeBytes.Add((byte)hash.hashSize);
95             codeBytes.AddRange(hash.hashBytes);
96         }
97
98         /// <summary>
99         /// Create new OP_PUSHDATAn operator and add it to opcode bytes list
100         /// </summary>
101         /// <param name="dataBytes">List of data bytes</param>
102         public void PushData(IList<byte> dataBytes)
103         {
104             long nCount = dataBytes.LongCount();
105
106             if (nCount < (int)opcodetype.OP_PUSHDATA1)
107             {
108                 // OP_0 and OP_FALSE
109                 codeBytes.Add((byte)nCount);
110             }
111             else if (nCount < 0xff)
112             {
113                 // OP_PUSHDATA1 0x01 [0x5a]
114                 codeBytes.Add((byte)opcodetype.OP_PUSHDATA1);
115                 codeBytes.Add((byte)nCount);
116             }
117             else if (nCount < 0xffff)
118             {
119                 // OP_PUSHDATA1 0x00 0x01 [0x5a]
120                 codeBytes.Add((byte)opcodetype.OP_PUSHDATA2);
121
122                 byte[] szBytes = BitConverter.GetBytes((ushort)nCount);
123                 if (BitConverter.IsLittleEndian)
124                 {
125                     Array.Reverse(szBytes);
126                 }
127                 codeBytes.AddRange(szBytes);
128             }
129             else if (nCount < 0xffffffff)
130             {
131                 // OP_PUSHDATA1 0x00 0x00 0x00 0x01 [0x5a]
132                 codeBytes.Add((byte)opcodetype.OP_PUSHDATA4);
133
134                 byte[] szBytes = BitConverter.GetBytes((uint)nCount);
135                 if (BitConverter.IsLittleEndian)
136                 {
137                     Array.Reverse(szBytes);
138                 }
139                 codeBytes.AddRange(szBytes);
140             }
141
142             // Add data bytes
143             codeBytes.AddRange(dataBytes);
144         }
145
146         /// <summary>
147         /// Scan code bytes for pattern
148         /// </summary>
149         /// <param name="pattern">Pattern sequence</param>
150         /// <returns>Matches enumerator</returns>
151         private IEnumerable<int> FindPattern(IList<byte> pattern)
152         {
153             for (int i = 0; i < codeBytes.Count; i++)
154             {
155                 if (codeBytes.Skip(i).Take(pattern.Count).SequenceEqual(pattern))
156                 {
157                     yield return i;
158                 }
159             }
160         }
161
162         /// <summary>
163         /// Scan code bytes for pattern and remove it
164         /// </summary>
165         /// <param name="pattern">Pattern sequence</param>
166         /// <returns>Matches number</returns>
167         public int RemovePattern(IList<byte> pattern)
168         {
169             List<byte> resultBytes = new List<byte>(codeBytes);
170             int count = 0;
171             int patternLen = pattern.Count;
172                         
173             foreach (int i in FindPattern(pattern))
174             {
175                 resultBytes.RemoveRange(i - count * patternLen, patternLen);
176                 count++;
177             }
178
179             codeBytes = resultBytes;
180             
181             return count;
182         }
183
184         /// <summary>
185         /// Is it true that script doesn't contain anything except push value operations?
186         /// </summary>
187         /// <returns>Checking result</returns>
188         public bool IsPushonly()
189         {
190             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
191
192             opcodetype opcode; // Current opcode
193             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
194             
195             // Scan opcodes sequence
196             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
197             {
198                 if (opcode > opcodetype.OP_16)
199                 {
200                     // We don't allow control opcodes here
201                     return false;
202                 }
203             }
204
205             return true;
206         }
207
208         /// <summary>
209         /// Is it true that script doesn't contain non-canonical push operations?
210         /// </summary>
211         /// <returns>Checking result</returns>
212         public bool HashOnlyCanonicalPushes()
213         {
214             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
215
216             opcodetype opcode; // Current opcode
217             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
218
219             // Scan opcodes sequence
220             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
221             {
222                 byte[] data = pushArgs.ToArray();
223
224                 if (opcode < opcodetype.OP_PUSHDATA1 && opcode > opcodetype.OP_0 && (data.Length == 1 && data[0] <= 16))
225                     // Could have used an OP_n code, rather than a 1-byte push.
226                     return false;
227                 if (opcode == opcodetype.OP_PUSHDATA1 && data.Length < (int)opcodetype.OP_PUSHDATA1)
228                     // Could have used a normal n-byte push, rather than OP_PUSHDATA1.
229                     return false;
230                 if (opcode == opcodetype.OP_PUSHDATA2 && data.Length <= 0xFF)
231                     // Could have used an OP_PUSHDATA1.
232                     return false;
233                 if (opcode == opcodetype.OP_PUSHDATA4 && data.LongLength <= 0xFFFF)
234                     // Could have used an OP_PUSHDATA2.
235                     return false;
236             }
237
238             return true;
239         }
240
241         /// <summary>
242         /// Quick test for pay-to-script-hash CScripts
243         /// </summary>
244         /// <returns>Checking result</returns>
245         public bool IsPayToScriptHash()
246         {
247             return (codeBytes.Count() == 23 &&
248                     codeBytes[0] == (byte)opcodetype.OP_HASH160 &&
249                     codeBytes[1] == 0x14 &&
250                     codeBytes[22] == (byte)opcodetype.OP_EQUAL);
251         }
252
253         /// <summary>
254         /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
255         /// as 20 sigops. With pay-to-script-hash, that changed:
256         /// CHECKMULTISIGs serialized in scriptSigs are
257         /// counted more accurately, assuming they are of the form
258         ///  ... OP_N CHECKMULTISIG ...
259         /// </summary>
260         /// <param name="fAccurate">Legacy mode flag</param>
261         /// <returns>Amount of sigops</returns>
262         public int GetSigOpCount(bool fAccurate)
263         {
264             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
265
266             opcodetype opcode; // Current opcode
267             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
268
269             int nCount = 0;
270             opcodetype lastOpcode = opcodetype.OP_INVALIDOPCODE;
271
272             // Scan opcodes sequence
273             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
274             {
275                 if (opcode == opcodetype.OP_CHECKSIG || opcode == opcodetype.OP_CHECKSIGVERIFY)
276                 {
277                     nCount++;
278                 }
279                 else if (opcode == opcodetype.OP_CHECKMULTISIG || opcode == opcodetype.OP_CHECKMULTISIGVERIFY)
280                 {
281                     if (fAccurate && lastOpcode >= opcodetype.OP_1 && lastOpcode <= opcodetype.OP_16)
282                     {
283                         nCount += ScriptOpcode.DecodeOP_N(lastOpcode);
284                     }
285                     else
286                     {
287                         nCount += 20;
288                     }
289                 }
290             }
291
292             return nCount;
293         }
294
295         /// <summary>
296         /// Accurately count sigOps, including sigOps in
297         /// pay-to-script-hash transactions
298         /// </summary>
299         /// <param name="scriptSig">pay-to-script-hash scriptPubKey</param>
300         /// <returns>SigOps count</returns>
301         public int GetSigOpCount(CScript scriptSig)
302         {
303             if (!IsPayToScriptHash())
304             {
305                 return GetSigOpCount(true);
306             }
307
308             // This is a pay-to-script-hash scriptPubKey;
309             // get the last item that the scriptSig
310             // pushes onto the stack:
311             WrappedList<byte> wScriptSig = scriptSig.GetWrappedList();
312
313             opcodetype opcode; // Current opcode
314             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
315
316             while (ScriptOpcode.GetOp(ref wScriptSig, out opcode, out pushArgs))
317             {
318                 if (opcode > opcodetype.OP_16)
319                     return 0;
320             }
321
322             /// ... and return its opcount:
323             CScript subScript = new CScript(pushArgs);
324
325             return subScript.GetSigOpCount(true);
326
327         }
328
329         public void SetDestination(CKeyID ID)
330         {
331             codeBytes.Clear();
332             AddOp(opcodetype.OP_DUP);
333             AddOp(opcodetype.OP_HASH160);
334             AddHash(ID);
335             AddOp(opcodetype.OP_EQUAL);
336         }
337
338         public void SetDestination(CScriptID ID)
339         {
340             codeBytes.Clear();
341             AddOp(opcodetype.OP_HASH160);
342             AddHash(ID);
343             AddOp(opcodetype.OP_EQUAL);
344         }
345
346         public void SetMultiSig(int nRequired, IEnumerable<CKey> keys)
347         {
348             codeBytes.Clear();
349             AddOp(ScriptOpcode.EncodeOP_N(nRequired));
350
351             foreach (CKey key in keys)
352             {
353                 PushData(key.GetPubKey().Raw);
354             }
355             AddOp(ScriptOpcode.EncodeOP_N(keys.Count()));
356             AddOp(opcodetype.OP_CHECKMULTISIG);
357         }
358
359         /// <summary>
360         /// Disassemble current script code
361         /// </summary>
362         /// <returns>Code listing</returns>
363                 public override string ToString()
364                 {
365                         StringBuilder sb = new StringBuilder();
366             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
367
368             opcodetype opcode; // Current opcode
369             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
370             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
371             {
372                 if (sb.Length != 0)
373                 {
374                     sb.Append(" ");
375                 }
376
377                 if (0 <= opcode && opcode <= opcodetype.OP_PUSHDATA4)
378                 {
379                     sb.Append(ScriptOpcode.ValueString(pushArgs));
380                 }
381                 else
382                 {
383                     sb.Append(ScriptOpcode.GetOpName(opcode));
384                 }
385             }
386
387             return sb.ToString();
388                 }
389         }
390 }
391