2 * Novacoin classes library
3 * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 using System.Collections.Generic;
26 public class CScriptException : Exception
28 public CScriptException()
32 public CScriptException(string message)
37 public CScriptException(string message, Exception inner)
38 : base(message, inner)
44 /// Representation of script code
48 private List<byte> codeBytes;
51 /// Initializes an empty instance of CScript
55 codeBytes = new List<byte>();
59 /// Initializes new instance of CScript and fills it with supplied bytes
61 /// <param name="bytes">Enumerator interface for byte sequence</param>
62 public CScript(byte[] bytes)
64 codeBytes = new List<byte>(bytes);
68 /// Return a new instance of ByteQueue object for current code bytes
70 /// <returns></returns>
71 public ByteQueue GetByteQUeue()
73 return new ByteQueue(codeBytes);
77 /// Adds specified operation to instruction list
79 /// <param name="opcode"></param>
80 public void AddInstruction(instruction opcode)
82 if (opcode < instruction.OP_0 || opcode > instruction.OP_INVALIDOPCODE)
84 throw new CScriptException("CScript::AddInstruction() : invalid instruction");
87 codeBytes.Add((byte)opcode);
91 /// Adds hash to instruction list.
92 /// New items are added in this format:
93 /// hash_length_byte hash_bytes
95 /// <param name="hash">Hash160 instance</param>
96 public void AddHash(Hash160 hash)
98 codeBytes.Add((byte)hash.hashSize);
99 codeBytes.AddRange(hash.hashBytes);
103 /// Adds hash to instruction list.
104 /// New items are added in this format:
105 /// hash_length_byte hash_bytes
107 /// <param name="hash">Hash256 instance</param>
108 public void AddHash(Hash256 hash)
110 codeBytes.Add((byte)hash.hashSize);
111 codeBytes.AddRange(hash.hashBytes);
115 /// Create new OP_PUSHDATAn operator and add it to instruction list
117 /// <param name="dataBytes">Set of data bytes</param>
118 public void PushData(byte[] dataBytes)
120 var nCount = dataBytes.LongLength;
122 if (nCount < (int)instruction.OP_PUSHDATA1)
125 codeBytes.Add((byte)nCount);
127 else if (nCount < 0xff)
129 // OP_PUSHDATA1 0x01 [0x5a]
130 codeBytes.Add((byte)instruction.OP_PUSHDATA1);
131 codeBytes.Add((byte)nCount);
133 else if (nCount < 0xffff)
135 // OP_PUSHDATA1 0x00 0x01 [0x5a]
136 codeBytes.Add((byte)instruction.OP_PUSHDATA2);
138 var szBytes = Interop.BEBytes((ushort)nCount);
139 codeBytes.AddRange(szBytes);
141 else if (nCount < 0xffffffff)
143 // OP_PUSHDATA1 0x00 0x00 0x00 0x01 [0x5a]
144 codeBytes.Add((byte)instruction.OP_PUSHDATA4);
146 var szBytes = Interop.BEBytes((uint)nCount);
147 codeBytes.AddRange(szBytes);
151 codeBytes.AddRange(dataBytes);
155 /// Scan code bytes for pattern
157 /// <param name="pattern">Pattern sequence</param>
158 /// <returns>Matches enumerator</returns>
159 private IEnumerable<int> FindPattern(byte[] pattern)
161 for (int i = 0; i < codeBytes.Count; i++)
163 if (codeBytes.Skip(i).Take(pattern.Length).SequenceEqual(pattern))
171 /// Scan code bytes for pattern and remove it
173 /// <param name="pattern">Pattern sequence</param>
174 /// <returns>Matches number</returns>
175 public int RemovePattern(byte[] pattern)
177 var resultBytes = new List<byte>(codeBytes);
180 foreach (int i in FindPattern(pattern))
182 resultBytes.RemoveRange(i - count * pattern.Length, pattern.Length);
186 codeBytes = resultBytes;
192 /// Is it true that script doesn't contain anything except push value operations?
194 public bool IsPushOnly
198 var wCodeBytes = new ByteQueue(codeBytes);
200 instruction opcode; // Current instruction
201 byte[] pushArgs; // OP_PUSHDATAn argument
203 // Scan instructions sequence
204 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
206 if (opcode > instruction.OP_16)
208 // We don't allow control instructions here
218 /// Is it true that script doesn't contain non-canonical push operations?
220 public bool HasOnlyCanonicalPushes
224 var wCodeBytes = new ByteQueue(codeBytes);
226 byte[] pushArgs; // OP_PUSHDATAn argument
227 instruction opcode; // Current instruction
229 // Scan instructions sequence
230 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
234 if (opcode < instruction.OP_PUSHDATA1 && opcode > instruction.OP_0 && (data.Length == 1 && data[0] <= 16))
236 // Could have used an OP_n code, rather than a 1-byte push.
239 if (opcode == instruction.OP_PUSHDATA1 && data.Length < (int)instruction.OP_PUSHDATA1)
241 // Could have used a normal n-byte push, rather than OP_PUSHDATA1.
244 if (opcode == instruction.OP_PUSHDATA2 && data.Length <= 0xFF)
246 // Could have used an OP_PUSHDATA1.
249 if (opcode == instruction.OP_PUSHDATA4 && data.LongLength <= 0xFFFF)
251 // Could have used an OP_PUSHDATA2.
261 /// Quick test for pay-to-script-hash CScripts
263 public bool IsPayToScriptHash
267 // Sender provides redeem script hash, receiver provides signature list and redeem script
268 // OP_HASH160 20 [20 byte hash] OP_EQUAL
269 return (codeBytes.Count() == 23 &&
270 codeBytes[0] == (byte)instruction.OP_HASH160 &&
271 codeBytes[1] == 0x14 && // 20 bytes hash length prefix
272 codeBytes[22] == (byte)instruction.OP_EQUAL);
277 /// Quick test for pay-to-pubkeyhash CScripts
279 public bool IsPayToPubKeyHash
283 // Sender provides hash of pubkey, receiver provides signature and pubkey
284 // OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG
285 return (codeBytes.Count == 25 &&
286 codeBytes[0] == (byte)instruction.OP_DUP &&
287 codeBytes[1] == (byte)instruction.OP_HASH160 &&
288 codeBytes[2] == 0x14 && // 20 bytes hash length prefix
289 codeBytes[23] == (byte)instruction.OP_EQUALVERIFY &&
290 codeBytes[24] == (byte)instruction.OP_CHECKSIG);
295 /// Quick test for Null destination
299 get { return codeBytes.Count == 0; }
303 /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
304 /// as 20 sigops. With pay-to-script-hash, that changed:
305 /// CHECKMULTISIGs serialized in scriptSigs are
306 /// counted more accurately, assuming they are of the form
307 /// ... OP_N CHECKMULTISIG ...
309 /// <param name="fAccurate">Legacy mode flag</param>
310 /// <returns>Amount of sigops</returns>
311 public int GetSigOpCount(bool fAccurate)
313 var wCodeBytes = new ByteQueue(codeBytes);
315 instruction opcode; // Current instruction
316 byte[] pushArgs; // OP_PUSHDATAn argument
319 var lastOpcode = instruction.OP_INVALIDOPCODE;
321 // Scan instructions sequence
322 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
324 if (opcode == instruction.OP_CHECKSIG || opcode == instruction.OP_CHECKSIGVERIFY)
328 else if (opcode == instruction.OP_CHECKMULTISIG || opcode == instruction.OP_CHECKMULTISIGVERIFY)
330 if (fAccurate && lastOpcode >= instruction.OP_1 && lastOpcode <= instruction.OP_16)
332 nCount += ScriptCode.DecodeOP_N(lastOpcode);
345 /// Accurately count sigOps, including sigOps in
346 /// pay-to-script-hash transactions
348 /// <param name="scriptSig">pay-to-script-hash scriptPubKey</param>
349 /// <returns>SigOps count</returns>
350 public int GetSigOpCount(CScript scriptSig)
352 if (!IsPayToScriptHash)
354 return GetSigOpCount(true);
357 // This is a pay-to-script-hash scriptPubKey;
358 // get the last item that the scriptSig
359 // pushes onto the stack:
360 ByteQueue wScriptSig = scriptSig.GetByteQUeue();
362 instruction opcode; // Current instruction
363 byte[] pushArgs; // OP_PUSHDATAn argument
365 while (ScriptCode.GetOp(ref wScriptSig, out opcode, out pushArgs))
367 if (opcode > instruction.OP_16)
373 /// ... and return its opcount:
374 var subScript = new CScript(pushArgs);
376 return subScript.GetSigOpCount(true);
381 /// Set pay-to-pubkey destination.
383 /// <param name="pubKey">Instance of CPubKey.</param>
384 public void SetDestination(CPubKey pubKey)
387 PushData(pubKey.PublicBytes);
388 AddInstruction(instruction.OP_CHECKSIG);
392 /// Set pay-to-pubkeyhash destination
394 /// <param name="ID">Public key hash</param>
395 public void SetDestination(CKeyID ID)
398 AddInstruction(instruction.OP_DUP);
399 AddInstruction(instruction.OP_HASH160);
401 AddInstruction(instruction.OP_EQUALVERIFY);
402 AddInstruction(instruction.OP_CHECKSIG);
406 /// Set pay-to-scripthash destination
408 /// <param name="ID">Script hash</param>
409 public void SetDestination(CScriptID ID)
412 AddInstruction(instruction.OP_HASH160);
414 AddInstruction(instruction.OP_EQUAL);
418 /// Reset script code buffer.
420 public void SetNullDestination()
426 /// Set multisig destination.
428 /// <param name="nRequired">Amount of required signatures.</param>
429 /// <param name="keys">Set of public keys.</param>
430 public void SetMultiSig(int nRequired, CPubKey[] keys)
433 AddInstruction(ScriptCode.EncodeOP_N(nRequired));
435 foreach (var key in keys)
437 PushData(key.PublicBytes);
440 AddInstruction(ScriptCode.EncodeOP_N(keys.Length));
441 AddInstruction(instruction.OP_CHECKMULTISIG);
445 /// Access to script code.
449 get { return codeBytes.ToArray(); }
452 public CScriptID ScriptID
454 get { return new CScriptID(Hash160.Compute160(codeBytes.ToArray())); }
458 /// Disassemble current script code
460 /// <returns>Code listing</returns>
461 public override string ToString()
463 var sb = new StringBuilder();
464 var wCodeBytes = new ByteQueue(codeBytes);
466 instruction opcode; // Current instruction
467 byte[] pushArgs; // OP_PUSHDATAn argument
468 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
475 if (0 <= opcode && opcode <= instruction.OP_PUSHDATA4)
477 sb.Append(ScriptCode.ValueString(pushArgs));
481 sb.Append(ScriptCode.GetOpName(opcode));
485 return sb.ToString();