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;
23 using System.Diagnostics.Contracts;
24 using System.Numerics;
29 /// Representation of script code
33 private List<byte> codeBytes;
36 /// Initializes an empty instance of CScript
40 codeBytes = new List<byte>();
44 /// Initializes new instance of CScript and fills it with supplied bytes
46 /// <param name="bytes">Enumerator interface for byte sequence</param>
47 public CScript(byte[] bytes)
49 codeBytes = new List<byte>(bytes);
53 /// Return a new instance of ByteQueue object for current code bytes
55 /// <returns></returns>
56 public InstructionQueue GetInstructionQueue()
58 return new InstructionQueue(ref codeBytes);
62 /// Add serialized number to instructions list.
64 /// <param name="n">Number to add.</param>
65 public void AddNumber(int n)
67 if (n == -1 || (n >= 1 && n <= 16))
69 codeBytes.Add((byte)ScriptCode.EncodeOP_N(n, true));
74 PushData(bn.ToByteArray());
79 /// Adds specified operation to instruction list
81 /// <param name="opcode">Instruction to add.</param>
82 public void AddInstruction(instruction opcode)
84 Contract.Requires<ArgumentException>(opcode >= instruction.OP_0 && opcode <= instruction.OP_INVALIDOPCODE, "Invalid instruction.");
86 codeBytes.Add((byte)opcode);
90 /// Adds hash to instruction list.
91 /// New items are added in this format:
92 /// hash_length_byte hash_bytes
94 /// <param name="hash">uint160 instance</param>
95 public void AddHash(uint160 hash)
97 codeBytes.Add((byte)hash.Size);
98 codeBytes.AddRange((byte[])hash);
102 /// Adds hash to instruction list.
103 /// New items are added in this format:
104 /// hash_length_byte hash_bytes
106 /// <param name="hash">uint256 instance</param>
107 public void AddHash(uint256 hash)
109 codeBytes.Add((byte)hash.Size);
110 codeBytes.AddRange((byte[])hash);
114 /// Create new OP_PUSHDATAn operator and add it to instruction list
116 /// <param name="dataBytes">Set of data bytes</param>
117 public void PushData(byte[] dataBytes)
119 var nCount = dataBytes.LongLength;
121 if (nCount < (int)instruction.OP_PUSHDATA1)
124 codeBytes.Add((byte)nCount);
126 else if (nCount < 0xff)
128 // OP_PUSHDATA1 0x01 [0x5a]
129 codeBytes.Add((byte)instruction.OP_PUSHDATA1);
130 codeBytes.Add((byte)nCount);
132 else if (nCount < 0xffff)
134 // OP_PUSHDATA1 0x01 0x00 [0x5a]
135 codeBytes.Add((byte)instruction.OP_PUSHDATA2);
137 var szBytes = BitConverter.GetBytes((ushort)nCount);
138 codeBytes.AddRange(szBytes);
140 else if (nCount < 0xffffffff)
142 // OP_PUSHDATA1 0x01 0x00 0x00 0x00 [0x5a]
143 codeBytes.Add((byte)instruction.OP_PUSHDATA4);
145 var szBytes = BitConverter.GetBytes((uint)nCount);
146 codeBytes.AddRange(szBytes);
150 codeBytes.AddRange(dataBytes);
154 /// Just insert data array without including any prefixes. Please make sure that you know what you're doing,
155 /// it is recommended to use AddInstruction, AddHash or PushData instead.
157 /// <param name="dataBytes">Data bytes</param>
158 public void AddRawData(byte[] dataBytes)
161 codeBytes.AddRange(dataBytes);
165 /// Scan pushed data bytes for pattern and, in case of exact match, remove it.
167 /// <param name="pattern">Pattern sequence</param>
168 /// <returns>Matches count</returns>
169 public int RemovePattern(byte[] pattern)
171 // There is no sense to continue if pattern is empty or longer than script itself
172 if (pattern.Length == 0 || pattern.Length > codeBytes.Count)
178 var bq1 = new InstructionQueue(ref codeBytes);
183 var newScript = new CScript();
185 while (ScriptCode.GetOp(ref bq1, out opcode, out pushData))
187 if (pushData.Length == 0)
189 // No data, put instruction on its place
190 newScript.AddInstruction(opcode);
192 else if (!pushData.SequenceEqual(pattern))
194 // No match, create push operator
195 newScript.PushData(pushData);
205 // Replace current script if any matches were found
206 codeBytes = newScript.codeBytes;
213 /// Scan script for specific instruction and remove it if there are some matches.
215 /// <param name="op">Instruction</param>
216 /// <returns>Matches count</returns>
217 public int RemoveInstruction(instruction op)
223 var newScript = new CScript();
224 var bq1 = new InstructionQueue(ref codeBytes);
226 while (ScriptCode.GetOp(ref bq1, out opcode, out pushData))
228 if (pushData.Length != 0 && op != opcode)
230 // If instruction didn't match then push its data again
231 newScript.PushData(pushData);
233 else if (Enum.IsDefined(typeof(instruction), op) && op != opcode)
235 // Instruction didn't match
236 newScript.AddInstruction(opcode);
246 // Replace current script if any matches were found
247 codeBytes = newScript.codeBytes;
254 /// Is it true that script doesn't contain anything except push value operations?
256 public bool IsPushOnly
260 var wCodeBytes = new InstructionQueue(ref codeBytes);
262 instruction opcode; // Current instruction
263 byte[] pushArgs; // OP_PUSHDATAn argument
265 // Scan instructions sequence
266 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
268 if (opcode > instruction.OP_16)
270 // We don't allow control instructions here
280 /// Is it true that script doesn't contain non-canonical push operations?
282 public bool HasOnlyCanonicalPushes
286 var wCodeBytes = new InstructionQueue(ref codeBytes);
288 byte[] pushArgs; // OP_PUSHDATAn argument
289 instruction opcode; // Current instruction
291 // Scan instructions sequence
292 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
296 if (opcode < instruction.OP_PUSHDATA1 && opcode > instruction.OP_0 && (data.Length == 1 && data[0] <= 16))
298 // Could have used an OP_n code, rather than a 1-byte push.
301 if (opcode == instruction.OP_PUSHDATA1 && data.Length < (int)instruction.OP_PUSHDATA1)
303 // Could have used a normal n-byte push, rather than OP_PUSHDATA1.
306 if (opcode == instruction.OP_PUSHDATA2 && data.Length <= 0xFF)
308 // Could have used an OP_PUSHDATA1.
311 if (opcode == instruction.OP_PUSHDATA4 && data.LongLength <= 0xFFFF)
313 // Could have used an OP_PUSHDATA2.
323 /// Quick test for pay-to-script-hash CScripts
325 public bool IsPayToScriptHash
329 // Sender provides redeem script hash, receiver provides signature list and redeem script
330 // OP_HASH160 20 [20 byte hash] OP_EQUAL
331 return (codeBytes.Count() == 23 &&
332 codeBytes[0] == (byte)instruction.OP_HASH160 &&
333 codeBytes[1] == 0x14 && // 20 bytes hash length prefix
334 codeBytes[22] == (byte)instruction.OP_EQUAL);
339 /// Quick test for pay-to-pubkeyhash CScripts
341 public bool IsPayToPubKeyHash
345 // Sender provides hash of pubkey, receiver provides signature and pubkey
346 // OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG
347 return (codeBytes.Count == 25 &&
348 codeBytes[0] == (byte)instruction.OP_DUP &&
349 codeBytes[1] == (byte)instruction.OP_HASH160 &&
350 codeBytes[2] == 0x14 && // 20 bytes hash length prefix
351 codeBytes[23] == (byte)instruction.OP_EQUALVERIFY &&
352 codeBytes[24] == (byte)instruction.OP_CHECKSIG);
357 /// Quick test for Null destination
361 get { return codeBytes.Count == 0; }
365 /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
366 /// as 20 sigops. With pay-to-script-hash, that changed:
367 /// CHECKMULTISIGs serialized in scriptSigs are
368 /// counted more accurately, assuming they are of the form
369 /// ... OP_N CHECKMULTISIG ...
371 /// <param name="fAccurate">Legacy mode flag</param>
372 /// <returns>Amount of sigops</returns>
373 public uint GetSigOpCount(bool fAccurate)
375 var wCodeBytes = new InstructionQueue(ref codeBytes);
377 instruction opcode; // Current instruction
378 byte[] pushArgs; // OP_PUSHDATAn argument
381 var lastOpcode = instruction.OP_INVALIDOPCODE;
383 // Scan instructions sequence
384 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
386 if (opcode == instruction.OP_CHECKSIG || opcode == instruction.OP_CHECKSIGVERIFY)
390 else if (opcode == instruction.OP_CHECKMULTISIG || opcode == instruction.OP_CHECKMULTISIGVERIFY)
392 if (fAccurate && lastOpcode >= instruction.OP_1 && lastOpcode <= instruction.OP_16)
394 nCount += (uint)ScriptCode.DecodeOP_N(lastOpcode);
407 /// Accurately count sigOps, including sigOps in
408 /// pay-to-script-hash transactions
410 /// <param name="scriptSig">pay-to-script-hash scriptPubKey</param>
411 /// <returns>SigOps count</returns>
412 public uint GetSigOpCount(CScript scriptSig)
414 if (!IsPayToScriptHash)
416 return GetSigOpCount(true);
419 // This is a pay-to-script-hash scriptPubKey;
420 // get the last item that the scriptSig
421 // pushes onto the stack:
422 InstructionQueue wScriptSig = scriptSig.GetInstructionQueue();
423 uint nScriptSigSize = scriptSig.Size;
425 instruction opcode; // Current instruction
426 byte[] pushArgs = new byte[0]; // OP_PUSHDATAn argument
429 while (wScriptSig.Index < nScriptSigSize)
431 if (!ScriptCode.GetOp(ref wScriptSig, out opcode, out pushArgs))
436 if (opcode > instruction.OP_16)
442 /// ... and return its opcount:
443 var subScript = new CScript(pushArgs);
445 return subScript.GetSigOpCount(true);
450 /// Set pay-to-pubkey destination.
452 /// <param name="pubKey">Instance of CPubKey.</param>
453 public void SetDestination(CPubKey pubKey)
457 AddInstruction(instruction.OP_CHECKSIG);
461 /// Set pay-to-pubkeyhash destination
463 /// <param name="ID">Public key hash</param>
464 public void SetDestination(CKeyID ID)
467 AddInstruction(instruction.OP_DUP);
468 AddInstruction(instruction.OP_HASH160);
470 AddInstruction(instruction.OP_EQUALVERIFY);
471 AddInstruction(instruction.OP_CHECKSIG);
475 /// Set pay-to-scripthash destination
477 /// <param name="ID">Script hash</param>
478 public void SetDestination(CScriptID ID)
481 AddInstruction(instruction.OP_HASH160);
483 AddInstruction(instruction.OP_EQUAL);
487 /// Reset script code buffer.
489 public void SetNullDestination()
495 /// Set multisig destination.
497 /// <param name="nRequired">Amount of required signatures.</param>
498 /// <param name="keys">Set of public keys.</param>
499 public void SetMultiSig(int nRequired, CPubKey[] keys)
502 AddInstruction(ScriptCode.EncodeOP_N(nRequired));
504 foreach (var key in keys)
509 AddInstruction(ScriptCode.EncodeOP_N(keys.Length));
510 AddInstruction(instruction.OP_CHECKMULTISIG);
514 /// Access to script code.
516 public static implicit operator byte[] (CScript script)
518 return script.codeBytes.ToArray();
522 /// Implicit cast of byte array to CScript.
524 /// <param name="scriptBytes"></param>
525 public static implicit operator CScript(byte[] scriptBytes)
527 return new CScript(scriptBytes);
535 get { return (uint) codeBytes.Count; }
538 public CScriptID ScriptID
540 get { return new CScriptID(CryptoUtils.ComputeHash160(codeBytes.ToArray())); }
544 /// Disassemble current script code
546 /// <returns>Code listing</returns>
547 public override string ToString()
549 var sb = new StringBuilder();
550 var wCodeBytes = new InstructionQueue(ref codeBytes);
552 instruction opcode; // Current instruction
553 byte[] pushArgs; // OP_PUSHDATAn argument
554 while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
561 if (0 <= opcode && opcode <= instruction.OP_PUSHDATA4)
563 sb.Append(ScriptCode.ValueString(pushArgs));
567 sb.Append(ScriptCode.GetOpName(opcode));
571 return sb.ToString();