GetSigOpCount, SetDestination, SetMultiSig + new stubs
[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             if (dataBytes.Count < (int)opcodetype.OP_PUSHDATA1)
105             {
106                 // OP_0 and OP_FALSE
107                 codeBytes.Add((byte)dataBytes.Count);
108             }
109             else if (dataBytes.Count < 0xff)
110             {
111                 // OP_PUSHDATA1 0x01 [0x5a]
112                 codeBytes.Add((byte)opcodetype.OP_PUSHDATA1);
113                 codeBytes.Add((byte)dataBytes.Count);
114             }
115             else if (dataBytes.Count < 0xffff)
116             {
117                 // OP_PUSHDATA1 0x00 0x01 [0x5a]
118                 codeBytes.Add((byte)opcodetype.OP_PUSHDATA2);
119
120                 byte[] szBytes = BitConverter.GetBytes((short)dataBytes.Count);
121                 if (BitConverter.IsLittleEndian)
122                 {
123                     Array.Reverse(szBytes);
124                 }
125                 codeBytes.AddRange(szBytes);
126             }
127             else if ((uint)dataBytes.Count < 0xffffffff)
128             {
129                 // OP_PUSHDATA1 0x00 0x00 0x00 0x01 [0x5a]
130                 codeBytes.Add((byte)opcodetype.OP_PUSHDATA4);
131
132                 byte[] szBytes = BitConverter.GetBytes((uint)dataBytes.Count);
133                 if (BitConverter.IsLittleEndian)
134                 {
135                     Array.Reverse(szBytes);
136                 }
137                 codeBytes.AddRange(szBytes);
138             }
139
140             // Add data bytes
141             codeBytes.AddRange(dataBytes);
142         }
143
144         /// <summary>
145         /// Scan code bytes for pattern
146         /// </summary>
147         /// <param name="pattern">Pattern sequence</param>
148         /// <returns>Matches enumerator</returns>
149         private IEnumerable<int> FindPattern(IList<byte> pattern)
150         {
151             for (int i = 0; i < codeBytes.Count; i++)
152             {
153                 if (codeBytes.Skip(i).Take(pattern.Count).SequenceEqual(pattern))
154                 {
155                     yield return i;
156                 }
157             }
158         }
159
160         /// <summary>
161         /// Scan code bytes for pattern and remove it
162         /// </summary>
163         /// <param name="pattern">Pattern sequence</param>
164         /// <returns>Matches number</returns>
165         public int RemovePattern(IList<byte> pattern)
166         {
167             List<byte> resultBytes = new List<byte>(codeBytes);
168             int count = 0;
169             int patternLen = pattern.Count;
170                         
171             foreach (int i in FindPattern(pattern))
172             {
173                 resultBytes.RemoveRange(i - count * patternLen, patternLen);
174                 count++;
175             }
176
177             codeBytes = resultBytes;
178             
179             return count;
180         }
181
182         /// <summary>
183         /// Is it true that script doesn't contain anything except push value operations?
184         /// </summary>
185         /// <returns>Checking result</returns>
186         public bool IsPushonly()
187         {
188             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
189
190             opcodetype opcode; // Current opcode
191             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
192             
193             // Scan opcodes sequence
194             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
195             {
196                 if (opcode > opcodetype.OP_16)
197                 {
198                     // We don't allow control opcodes here
199                     return false;
200                 }
201             }
202
203             return true;
204         }
205
206         /// <summary>
207         /// Is it true that script doesn't contain non-canonical push operations?
208         /// </summary>
209         /// <returns>Checking result</returns>
210         public bool HashOnlyCanonicalPushes()
211         {
212             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
213
214             opcodetype opcode; // Current opcode
215             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
216
217             // Scan opcodes sequence
218             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
219             {
220                 byte[] data = pushArgs.ToArray();
221
222                 if (opcode < opcodetype.OP_PUSHDATA1 && opcode > opcodetype.OP_0 && (data.Length == 1 && data[0] <= 16))
223                     // Could have used an OP_n code, rather than a 1-byte push.
224                     return false;
225                 if (opcode == opcodetype.OP_PUSHDATA1 && data.Length < (int)opcodetype.OP_PUSHDATA1)
226                     // Could have used a normal n-byte push, rather than OP_PUSHDATA1.
227                     return false;
228                 if (opcode == opcodetype.OP_PUSHDATA2 && data.Length <= 0xFF)
229                     // Could have used an OP_PUSHDATA1.
230                     return false;
231                 if (opcode == opcodetype.OP_PUSHDATA4 && data.Length <= 0xFFFF)
232                     // Could have used an OP_PUSHDATA2.
233                     return false;
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                     return 0;
318             }
319
320             /// ... and return its opcount:
321             CScript subScript = new CScript(pushArgs);
322
323             return subScript.GetSigOpCount(true);
324
325         }
326
327         public void SetDestination(CKeyID ID)
328         {
329             codeBytes.Clear();
330             AddOp(opcodetype.OP_DUP);
331             AddOp(opcodetype.OP_HASH160);
332             AddHash(ID);
333             AddOp(opcodetype.OP_EQUAL);
334         }
335
336         public void SetDestination(CScriptID ID)
337         {
338             codeBytes.Clear();
339             AddOp(opcodetype.OP_HASH160);
340             AddHash(ID);
341             AddOp(opcodetype.OP_EQUAL);
342         }
343
344         public void SetMultiSig(int nRequired, IEnumerable<CKey> keys)
345         {
346             codeBytes.Clear();
347             AddOp(ScriptOpcode.EncodeOP_N(nRequired));
348
349             foreach (CKey key in keys)
350             {
351                 PushData(key.GetPubKey().Raw);
352             }
353             AddOp(ScriptOpcode.EncodeOP_N(keys.Count()));
354             AddOp(opcodetype.OP_CHECKMULTISIG);
355         }
356
357         /// <summary>
358         /// Disassemble current script code
359         /// </summary>
360         /// <returns>Code listing</returns>
361                 public override string ToString()
362                 {
363                         StringBuilder sb = new StringBuilder();
364             WrappedList<byte> wCodeBytes = new WrappedList<byte>(codeBytes);
365
366             opcodetype opcode; // Current opcode
367             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
368             while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
369             {
370                 if (sb.Length != 0)
371                 {
372                     sb.Append(" ");
373                 }
374
375                 if (0 <= opcode && opcode <= opcodetype.OP_PUSHDATA4)
376                 {
377                     sb.Append(ScriptOpcode.ValueString(pushArgs));
378                 }
379                 else
380                 {
381                     sb.Append(ScriptOpcode.GetOpName(opcode));
382                 }
383             }
384
385             return sb.ToString();
386                 }
387         }
388 }
389