Stack machine helpers
[NovacoinLibrary.git] / Novacoin / ScriptCode.cs
index 6d69349..45f8098 100644 (file)
@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Numerics;
 
 namespace Novacoin
 {
@@ -167,8 +168,32 @@ namespace Novacoin
         TX_NULL_DATA,
     };
 
+    /// <summary>
+    /// Signature hash types/flags
+    /// </summary>
+    public enum sigflag
+    {
+        SIGHASH_ALL = 1,
+        SIGHASH_NONE = 2,
+        SIGHASH_SINGLE = 3,
+        SIGHASH_ANYONECANPAY = 0x80,
+    };
+
     public static class ScriptCode
     {
+        public static string GetTxnOutputType(txnouttype t)
+        {
+            switch (t)
+            {
+                case txnouttype.TX_NONSTANDARD: return "nonstandard";
+                case txnouttype.TX_PUBKEY: return "pubkey";
+                case txnouttype.TX_PUBKEYHASH: return "pubkeyhash";
+                case txnouttype.TX_SCRIPTHASH: return "scripthash";
+                case txnouttype.TX_MULTISIG: return "multisig";
+                case txnouttype.TX_NULL_DATA: return "nulldata";
+            }
+            return string.Empty;
+        }
 
         /// <summary>
         /// Get the name of supplied opcode
@@ -435,11 +460,11 @@ namespace Novacoin
         /// <summary>
         /// Get next opcode from passed list of bytes and extract push arguments if there are some.
         /// </summary>
-        /// <param name="codeBytes">WrappedList reference.</param>
+        /// <param name="codeBytes">ByteQueue reference.</param>
         /// <param name="opcodeRet">Found opcode.</param>
         /// <param name="bytesRet">IEnumerable out param which is used to get the push arguments.</param>
         /// <returns>Result of operation</returns>
-        public static bool GetOp(ref WrappedList<byte> codeBytes, out opcodetype opcodeRet, out IEnumerable<byte> bytesRet)
+        public static bool GetOp(ref ByteQueue codeBytes, out opcodetype opcodeRet, out IEnumerable<byte> bytesRet)
         {
             bytesRet = new List<byte>();
             opcodeRet = opcodetype.OP_INVALIDOPCODE;
@@ -449,7 +474,7 @@ namespace Novacoin
             try
             {
                 // Read instruction
-                opcode = (opcodetype)codeBytes.GetItem();
+                opcode = (opcodetype)codeBytes.Get();
             }
             catch (WrappedListException)
             {
@@ -473,19 +498,19 @@ namespace Novacoin
                     {
                         // The next byte contains the number of bytes to be pushed onto the stack, 
                         //    i.e. you have something like OP_PUSHDATA1 0x01 [0x5a]
-                        szBytes[3] = (byte)codeBytes.GetItem();
+                        szBytes[3] = (byte)codeBytes.Get();
                     }
                     else if (opcode == opcodetype.OP_PUSHDATA2)
                     {
                         // The next two bytes contain the number of bytes to be pushed onto the stack,
                         //    i.e. now your operation will seem like this: OP_PUSHDATA2 0x00 0x01 [0x5a]
-                        codeBytes.GetItems(2).CopyTo(szBytes, 2);
+                        codeBytes.Get(2).CopyTo(szBytes, 2);
                     }
                     else if (opcode == opcodetype.OP_PUSHDATA4)
                     {
                         // The next four bytes contain the number of bytes to be pushed onto the stack,
                         //   OP_PUSHDATA4 0x00 0x00 0x00 0x01 [0x5a]
-                        szBytes = codeBytes.GetItems(4);
+                        szBytes = codeBytes.Get(4);
                     }
                 }
                 catch (WrappedListException)
@@ -502,7 +527,7 @@ namespace Novacoin
                     try
                     {
                         // Read found number of bytes into list of OP_PUSHDATAn arguments.
-                        bytesRet = codeBytes.GetEnumerableItems(nSize);
+                        bytesRet = codeBytes.GetEnumerable(nSize);
                     }
                     catch (WrappedListException)
                     {
@@ -612,7 +637,12 @@ namespace Novacoin
             return -1;
         }
 
-
+        /// <summary>
+        /// Is it a standart type of scriptPubKey?
+        /// </summary>
+        /// <param name="scriptPubKey">CScript instance</param>
+        /// <param name="whichType">utut type</param>
+        /// <returns>Checking result</returns>
         public static bool IsStandard(CScript scriptPubKey, out txnouttype whichType)
         {
             IList<IEnumerable<byte>> solutions = new List<IEnumerable<byte>>();
@@ -625,6 +655,7 @@ namespace Novacoin
 
             if (whichType == txnouttype.TX_MULTISIG)
             {
+                // Additional verification of OP_CHECKMULTISIG arguments
                 byte m = solutions.First().First();
                 byte n = solutions.Last().First();
 
@@ -661,7 +692,7 @@ namespace Novacoin
                 typeRet = txnouttype.TX_SCRIPTHASH;
 
                 // Take 20 bytes with offset of 2 bytes
-                IEnumerable<byte> hashBytes = scriptPubKey.Enumerable.Skip(2).Take(20);
+                IEnumerable<byte> hashBytes = scriptPubKey.Bytes.Skip(2).Take(20);
                 solutions.Add(hashBytes);
 
                 return true;
@@ -673,7 +704,7 @@ namespace Novacoin
                 typeRet = txnouttype.TX_PUBKEYHASH;
 
                 // Take 20 bytes with offset of 3 bytes
-                IEnumerable<byte> hashBytes = scriptPubKey.Enumerable.Skip(3).Take(20);
+                IEnumerable<byte> hashBytes = scriptPubKey.Bytes.Skip(3).Take(20);
                 solutions.Add(hashBytes);
 
                 return true;
@@ -717,17 +748,17 @@ namespace Novacoin
                 opcodetype opcode1, opcode2;
 
                 // Compare
-                WrappedList<byte> wl1 = script1.GetWrappedList();
-                WrappedList<byte> wl2 = script2.GetWrappedList();
+                ByteQueue wl1 = script1.GetWrappedList();
+                ByteQueue wl2 = script2.GetWrappedList();
 
                 IEnumerable<byte> args1, args2;
 
-                byte last1 = script1.Enumerable.Last();
-                byte last2 = script2.Enumerable.Last();
+                byte last1 = script1.Bytes.Last();
+                byte last2 = script2.Bytes.Last();
 
                 while (true)
                 {
-                    if (wl1.GetCurrentItem() == last1 && wl2.GetCurrentItem() == last2)
+                    if (wl1.GetCurrent() == last1 && wl2.GetCurrent() == last2)
                     {
                         // Found a match
                         typeRet = templateTuple.Item1;
@@ -820,5 +851,187 @@ namespace Novacoin
 
             return false;
         }
+
+        public static Hash256 SignatureHash(CScript scriptCode, CTransaction txTo, int nIn, int nHashType)
+        {
+            if (nIn >= txTo.vin.Length)
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.AppendFormat("ERROR: SignatureHash() : nIn={0} out of range\n", nIn);
+                throw new ArgumentOutOfRangeException("nIn", sb.ToString());
+            }
+
+            CTransaction txTmp = new CTransaction(txTo);
+
+            // In case concatenating two scripts ends up with two codeseparators,
+            // or an extra one at the end, this prevents all those possible incompatibilities.
+            scriptCode.RemovePattern(new byte[] { (byte)opcodetype.OP_CODESEPARATOR });
+
+            // Blank out other inputs' signatures
+            for (int i = 0; i < txTmp.vin.Length; i++)
+            {
+                txTmp.vin[i].scriptSig = new CScript();
+            }
+            txTmp.vin[nIn].scriptSig = scriptCode;
+
+            // Blank out some of the outputs
+            if ((nHashType & 0x1f) == (int)sigflag.SIGHASH_NONE)
+            {
+                // Wildcard payee
+                txTmp.vout = new CTxOut[0];
+
+                // Let the others update at will
+                for (int i = 0; i < txTmp.vin.Length; i++)
+                {
+                    if (i != nIn)
+                    {
+                        txTmp.vin[i].nSequence = 0;
+                    }
+                }
+            }
+            else if ((nHashType & 0x1f) == (int)sigflag.SIGHASH_SINGLE)
+            {
+                // Only lock-in the txout payee at same index as txin
+                int nOut = nIn;
+                if (nOut >= txTmp.vout.Length)
+                {
+                    StringBuilder sb = new StringBuilder();
+                    sb.AppendFormat("ERROR: SignatureHash() : nOut={0} out of range\n", nOut);
+                    throw new ArgumentOutOfRangeException("nOut", sb.ToString());
+                }
+                Array.Resize(ref txTmp.vout, nOut + 1);
+
+                for (int i = 0; i < nOut; i++)
+                {
+                    txTmp.vout[i] = new CTxOut();
+                }
+
+                // Let the others update at will
+                for (int i = 0; i < txTmp.vin.Length; i++)
+                {
+                    if (i != nIn)
+                    {
+                        txTmp.vin[i].nSequence = 0;
+                    }
+                }
+            }
+
+            // Blank out other inputs completely, not recommended for open transactions
+            if ((nHashType & (int)sigflag.SIGHASH_ANYONECANPAY) != 0)
+            {
+                txTmp.vin[0] = txTmp.vin[nIn];
+                Array.Resize(ref txTmp.vin, 1);
+            }
+
+            // Serialize and hash
+            List<byte> b = new List<byte>();
+            b.AddRange(txTmp.Bytes);
+            b.AddRange(BitConverter.GetBytes(nHashType));
+
+            return Hash256.Compute256(b);
+        }
+
+        public class StackMachineException : Exception
+        {
+            public StackMachineException()
+            {
+            }
+
+            public StackMachineException(string message)
+                : base(message)
+            {
+            }
+
+            public StackMachineException(string message, Exception inner)
+                : base(message, inner)
+            {
+            }
+        }
+
+
+        //
+        // Script is a stack machine (like Forth) that evaluates a predicate
+        // returning a bool indicating valid or not.  There are no loops.
+        //
+
+        /// <summary>
+        /// Remove last element from stack
+        /// </summary>
+        /// <param name="stack">Stack reference</param>
+        static void popstack(ref List<IEnumerable<byte>> stack)
+        {
+            int nCount = stack.Count;
+            if (nCount == 0)
+                throw new StackMachineException("popstack() : stack empty");
+            stack.RemoveAt(nCount - 1);
+        }
+
+        /// <summary>
+        /// Get element at specified stack depth
+        /// </summary>
+        /// <param name="stack">Stack reference</param>
+        /// <param name="nDepth">Depth</param>
+        /// <returns>Byte sequence</returns>
+        static IEnumerable<byte> stacktop(ref List<IEnumerable<byte>> stack, int nDepth)
+        {
+            int nStackElement = stack.Count + nDepth;
+
+            if (nDepth >= 0)
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.AppendFormat("stacktop() : positive depth ({0})", nDepth);
+
+                throw new StackMachineException(sb.ToString());
+            }
+
+            if (nStackElement < 0)
+            {
+                StringBuilder sb = new StringBuilder();
+                sb.AppendFormat("stacktop() : nDepth={0} exceeds real stack depth", nDepth);
+
+                throw new StackMachineException(sb.ToString());
+            }
+
+            return stack[nStackElement];
+        }
+
+        /// <summary>
+        /// Cast argument to boolean value
+        /// </summary>
+        /// <param name="value">Some byte sequence</param>
+        /// <returns></returns>
+        private static bool CastToBool(IEnumerable<byte> arg)
+        {
+            byte[] value = arg.ToArray();
+
+            for (var i = 0; i < value.Length; i++)
+            {
+                if (value[i] != 0)
+                {
+                    // Can be negative zero
+                    if (i == value.Length - 1 && value[i] == 0x80)
+                        return false;
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Cast argument to integer value
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        private static BigInteger CastToBigInteger(IEnumerable<byte> value)
+        {
+            if (value.Count() > 4)
+            {
+                throw new StackMachineException("CastToBigNum() : overflow");
+            }
+
+            return new BigInteger(value.ToArray());
+        }
     };
 }