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