Generic class was a bit excessive for our purposes
[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 ByteQueue object for current code bytes
51         /// </summary>
52         /// <returns></returns>
53         public ByteQueue GetWrappedList()
54         {
55              return new ByteQueue(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">Set of data bytes</param>
100         public void PushData(IEnumerable<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         public bool IsPushonly
178         {
179             get
180             {
181                 ByteQueue wCodeBytes = new ByteQueue(codeBytes);
182
183                 opcodetype opcode; // Current opcode
184                 IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
185
186                 // Scan opcodes sequence
187                 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
188                 {
189                     if (opcode > opcodetype.OP_16)
190                     {
191                         // We don't allow control opcodes here
192                         return false;
193                     }
194                 }
195
196                 return true;
197             }
198         }
199
200         /// <summary>
201         /// Is it true that script doesn't contain non-canonical push operations?
202         /// </summary>
203         public bool HasOnlyCanonicalPushes
204         {
205             get
206             {
207                 ByteQueue wCodeBytes = new ByteQueue(codeBytes);
208
209                 opcodetype opcode; // Current opcode
210                 IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
211
212                 // Scan opcodes sequence
213                 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
214                 {
215                     byte[] data = pushArgs.ToArray();
216
217                     if (opcode < opcodetype.OP_PUSHDATA1 && opcode > opcodetype.OP_0 && (data.Length == 1 && data[0] <= 16))
218                     {
219                         // Could have used an OP_n code, rather than a 1-byte push.
220                         return false;
221                     }
222                     if (opcode == opcodetype.OP_PUSHDATA1 && data.Length < (int)opcodetype.OP_PUSHDATA1)
223                     {
224                         // Could have used a normal n-byte push, rather than OP_PUSHDATA1.
225                         return false;
226                     }
227                     if (opcode == opcodetype.OP_PUSHDATA2 && data.Length <= 0xFF)
228                     {
229                         // Could have used an OP_PUSHDATA1.
230                         return false;
231                     }
232                     if (opcode == opcodetype.OP_PUSHDATA4 && data.LongLength <= 0xFFFF)
233                     {
234                         // Could have used an OP_PUSHDATA2.
235                         return false;
236                     }
237                 }
238
239                 return true;
240             }
241         }
242
243         /// <summary>
244         /// Quick test for pay-to-script-hash CScripts
245         /// </summary>
246         public bool IsPayToScriptHash
247         {
248             get
249             {
250                 // Sender provides redeem script hash, receiver provides signature list and redeem script
251                 // OP_HASH160 20 [20 byte hash] OP_EQUAL
252                 return (codeBytes.Count() == 23 &&
253                         codeBytes[0] == (byte)opcodetype.OP_HASH160 &&
254                         codeBytes[1] == 0x14 && // 20 bytes hash length prefix
255                         codeBytes[22] == (byte)opcodetype.OP_EQUAL);
256             }
257         }
258
259         /// <summary>
260         /// Quick test for pay-to-pubkeyhash CScripts
261         /// </summary>
262         public bool IsPayToPubKeyHash
263         {
264             get
265             {
266                 // Sender provides hash of pubkey, receiver provides signature and pubkey
267                 // OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG
268                 return (codeBytes.Count == 25 &&
269                         codeBytes[0] == (byte)opcodetype.OP_DUP &&
270                         codeBytes[1] == (byte)opcodetype.OP_HASH160 &&
271                         codeBytes[2] == 0x14 && // 20 bytes hash length prefix
272                         codeBytes[23] == (byte)opcodetype.OP_EQUALVERIFY &&
273                         codeBytes[24] == (byte)opcodetype.OP_CHECKSIG);
274             }
275         }
276
277         /// <summary>
278         /// Quick test for Null destination
279         /// </summary>
280         public bool IsNull
281         {
282             get { return codeBytes.Count == 0; }
283         }
284
285         /// <summary>
286         /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
287         /// as 20 sigops. With pay-to-script-hash, that changed:
288         /// CHECKMULTISIGs serialized in scriptSigs are
289         /// counted more accurately, assuming they are of the form
290         ///  ... OP_N CHECKMULTISIG ...
291         /// </summary>
292         /// <param name="fAccurate">Legacy mode flag</param>
293         /// <returns>Amount of sigops</returns>
294         public int GetSigOpCount(bool fAccurate)
295         {
296             ByteQueue wCodeBytes = new ByteQueue(codeBytes);
297
298             opcodetype opcode; // Current opcode
299             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
300
301             int nCount = 0;
302             opcodetype lastOpcode = opcodetype.OP_INVALIDOPCODE;
303
304             // Scan opcodes sequence
305             while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
306             {
307                 if (opcode == opcodetype.OP_CHECKSIG || opcode == opcodetype.OP_CHECKSIGVERIFY)
308                 {
309                     nCount++;
310                 }
311                 else if (opcode == opcodetype.OP_CHECKMULTISIG || opcode == opcodetype.OP_CHECKMULTISIGVERIFY)
312                 {
313                     if (fAccurate && lastOpcode >= opcodetype.OP_1 && lastOpcode <= opcodetype.OP_16)
314                     {
315                         nCount += ScriptCode.DecodeOP_N(lastOpcode);
316                     }
317                     else
318                     {
319                         nCount += 20;
320                     }
321                 }
322             }
323
324             return nCount;
325         }
326
327         /// <summary>
328         /// Accurately count sigOps, including sigOps in
329         /// pay-to-script-hash transactions
330         /// </summary>
331         /// <param name="scriptSig">pay-to-script-hash scriptPubKey</param>
332         /// <returns>SigOps count</returns>
333         public int GetSigOpCount(CScript scriptSig)
334         {
335             if (!IsPayToScriptHash)
336             {
337                 return GetSigOpCount(true);
338             }
339
340             // This is a pay-to-script-hash scriptPubKey;
341             // get the last item that the scriptSig
342             // pushes onto the stack:
343             ByteQueue wScriptSig = scriptSig.GetWrappedList();
344
345             opcodetype opcode; // Current opcode
346             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
347
348             while (ScriptCode.GetOp(ref wScriptSig, out opcode, out pushArgs))
349             {
350                 if (opcode > opcodetype.OP_16)
351                 {
352                     return 0;
353                 }
354             }
355
356             /// ... and return its opcount:
357             CScript subScript = new CScript(pushArgs);
358
359             return subScript.GetSigOpCount(true);
360
361         }
362
363         /// <summary>
364         /// Set pay-to-pubkey destination.
365         /// </summary>
366         /// <param name="pubKey">Instance of CPubKey.</param>
367         public void SetDestination(CPubKey pubKey)
368         {
369             codeBytes.Clear();
370             PushData(pubKey.PublicBytes);
371             AddOp(opcodetype.OP_CHECKSIG);
372         }
373
374         /// <summary>
375         /// Set pay-to-pubkeyhash destination
376         /// </summary>
377         /// <param name="ID">Public key hash</param>
378         public void SetDestination(CKeyID ID)
379         {
380             codeBytes.Clear();
381             AddOp(opcodetype.OP_DUP);
382             AddOp(opcodetype.OP_HASH160);
383             AddHash(ID);
384             AddOp(opcodetype.OP_EQUALVERIFY);
385             AddOp(opcodetype.OP_CHECKSIG);
386         }
387
388         /// <summary>
389         /// Set pay-to-scripthash destination
390         /// </summary>
391         /// <param name="ID">Script hash</param>
392         public void SetDestination(CScriptID ID)
393         {
394             codeBytes.Clear();
395             AddOp(opcodetype.OP_HASH160);
396             AddHash(ID);
397             AddOp(opcodetype.OP_EQUAL);
398         }
399
400         /// <summary>
401         /// Reset script code buffer.
402         /// </summary>
403         public void SetNullDestination()
404         {
405             codeBytes.Clear();
406         }
407
408         /// <summary>
409         /// Set multisig destination.
410         /// </summary>
411         /// <param name="nRequired">Amount of required signatures.</param>
412         /// <param name="keys">Set of public keys.</param>
413         public void SetMultiSig(int nRequired, IEnumerable<CPubKey> keys)
414         {
415             codeBytes.Clear();
416             AddOp(ScriptCode.EncodeOP_N(nRequired));
417
418             foreach (CPubKey key in keys)
419             {
420                 PushData(key.PublicBytes.ToList());
421             }
422
423             AddOp(ScriptCode.EncodeOP_N(keys.Count()));
424             AddOp(opcodetype.OP_CHECKMULTISIG);
425         }
426
427         /// <summary>
428         /// Access to script code.
429         /// </summary>
430         public IEnumerable<byte> Bytes
431         {
432             get { return codeBytes; }
433         }
434
435         public CScriptID ScriptID
436         {
437             get { return new CScriptID(Hash160.Compute160(codeBytes)); }
438         }
439
440         /// <summary>
441         /// Disassemble current script code
442         /// </summary>
443         /// <returns>Code listing</returns>
444                 public override string ToString()
445                 {
446                         StringBuilder sb = new StringBuilder();
447             ByteQueue wCodeBytes = new ByteQueue(codeBytes);
448
449             opcodetype opcode; // Current opcode
450             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
451             while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
452             {
453                 if (sb.Length != 0)
454                 {
455                     sb.Append(" ");
456                 }
457
458                 if (0 <= opcode && opcode <= opcodetype.OP_PUSHDATA4)
459                 {
460                     sb.Append(ScriptCode.ValueString(pushArgs));
461                 }
462                 else
463                 {
464                     sb.Append(ScriptCode.GetOpName(opcode));
465                 }
466             }
467
468             return sb.ToString();
469                 }
470         }
471 }
472