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