Generic class was a bit excessive for our purposes
[NovacoinLibrary.git] / Novacoin / ScriptCode.cs
index 5001a3b..17d79a8 100644 (file)
@@ -167,9 +167,33 @@ 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
         /// </summary>
@@ -435,11 +459,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 +473,7 @@ namespace Novacoin
             try
             {
                 // Read instruction
-                opcode = (opcodetype)codeBytes.GetItem();
+                opcode = (opcodetype)codeBytes.Get();
             }
             catch (WrappedListException)
             {
@@ -460,7 +484,7 @@ namespace Novacoin
             // Immediate operand
             if (opcode <= opcodetype.OP_PUSHDATA4)
             {
-                byte[] szBytes = new byte[4] {0, 0, 0, 0}; // Zero length
+                byte[] szBytes = new byte[4] { 0, 0, 0, 0 }; // Zero length
 
                 try
                 {
@@ -473,19 +497,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 +526,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)
                     {
@@ -530,7 +554,7 @@ namespace Novacoin
 
             if (bytes.Count() <= 4)
             {
-                byte[] valueBytes = new byte[4] {0, 0, 0, 0};
+                byte[] valueBytes = new byte[4] { 0, 0, 0, 0 };
                 bytes.ToArray().CopyTo(valueBytes, valueBytes.Length - bytes.Count());
 
                 sb.Append(Interop.BEBytesToUInt32(valueBytes));
@@ -551,7 +575,7 @@ namespace Novacoin
         public static string StackString(IList<IList<byte>> stackList)
         {
             StringBuilder sb = new StringBuilder();
-            foreach(IList<byte> bytesList in stackList)
+            foreach (IList<byte> bytesList in stackList)
             {
                 sb.Append(ValueString(bytesList));
             }
@@ -590,6 +614,71 @@ namespace Novacoin
             return (opcodetype)(opcodetype.OP_1 + n - 1);
         }
 
+        public static int ScriptSigArgsExpected(txnouttype t, IList<IEnumerable<byte>> solutions)
+        {
+            switch (t)
+            {
+                case txnouttype.TX_NONSTANDARD:
+                    return -1;
+                case txnouttype.TX_NULL_DATA:
+                    return 1;
+                case txnouttype.TX_PUBKEY:
+                    return 1;
+                case txnouttype.TX_PUBKEYHASH:
+                    return 2;
+                case txnouttype.TX_MULTISIG:
+                    if (solutions.Count() < 1 || solutions.First().Count() < 1)
+                        return -1;
+                    return solutions.First().First() + 1;
+                case txnouttype.TX_SCRIPTHASH:
+                    return 1; // doesn't include args needed by the script
+            }
+            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>>();
+
+            if (!Solver(scriptPubKey, out whichType, out solutions))
+            {
+                // No solutions found
+                return false;
+            }
+
+            if (whichType == txnouttype.TX_MULTISIG)
+            {
+                // Additional verification of OP_CHECKMULTISIG arguments
+                byte m = solutions.First().First();
+                byte n = solutions.Last().First();
+
+                // Support up to x-of-3 multisig txns as standard
+                if (n < 1 || n > 3)
+                {
+                    return false;
+                }
+                if (m < 1 || m > n)
+                {
+                    return false;
+                }
+            }
+
+            return whichType != txnouttype.TX_NONSTANDARD;
+        }
+
+        /// <summary>
+        /// Return public keys or hashes from scriptPubKey, for 'standard' transaction types.
+        /// </summary>
+        /// <param name="scriptPubKey">CScript instance</param>
+        /// <param name="typeRet">Output type</param>
+        /// <param name="solutions">Set of solutions</param>
+        /// <returns>Result</returns>
         public static bool Solver(CScript scriptPubKey, out txnouttype typeRet, out IList<IEnumerable<byte>> solutions)
         {
             solutions = new List<IEnumerable<byte>>();
@@ -597,24 +686,24 @@ namespace Novacoin
             // There are shortcuts for pay-to-script-hash and pay-to-pubkey-hash, which are more constrained than the other types:
 
             // It is always OP_HASH160 20 [20 byte hash] OP_EQUAL
-            if (scriptPubKey.IsPayToScriptHash())
+            if (scriptPubKey.IsPayToScriptHash)
             {
                 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;
             }
 
             // It is always OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG
-            if (scriptPubKey.IsPayToPubKeyHash())
+            if (scriptPubKey.IsPayToPubKeyHash)
             {
                 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;
@@ -626,7 +715,7 @@ namespace Novacoin
             // [ECDSA public key] OP_CHECKSIG
             templateTuples.Add(
                 new Tuple<txnouttype, IEnumerable<byte>>(
-                    txnouttype.TX_PUBKEY, 
+                    txnouttype.TX_PUBKEY,
                     new byte[] { (byte)opcodetype.OP_PUBKEY, (byte)opcodetype.OP_CHECKSIG })
             );
 
@@ -643,7 +732,7 @@ namespace Novacoin
             // OP_RETURN [up to 80 bytes of data]
             templateTuples.Add(
                 new Tuple<txnouttype, IEnumerable<byte>>(
-                    txnouttype.TX_NULL_DATA, 
+                    txnouttype.TX_NULL_DATA,
                     new byte[] { (byte)opcodetype.OP_RETURN, (byte)opcodetype.OP_SMALLDATA })
             );
 
@@ -652,23 +741,23 @@ namespace Novacoin
 
             foreach (Tuple<txnouttype, IEnumerable<byte>> templateTuple in templateTuples)
             {
-                CScript script2 = new CScript(templateTuple.Item2);
                 CScript script1 = scriptPubKey;
+                CScript script2 = new CScript(templateTuple.Item2);
 
                 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.GetItem() == last1 && wl2.GetItem() == last2)
+                    if (wl1.GetCurrent() == last1 && wl2.GetCurrent() == last2)
                     {
                         // Found a match
                         typeRet = templateTuple.Item1;
@@ -728,7 +817,7 @@ namespace Novacoin
                         solutions.Add(args1);
                     }
                     else if (opcode2 == opcodetype.OP_SMALLINTEGER)
-                    {   
+                    {
                         // Single-byte small integer pushed onto solutions
                         if (opcode1 == opcodetype.OP_0 || (opcode1 >= opcodetype.OP_1 && opcode1 <= opcodetype.OP_16))
                         {
@@ -748,7 +837,7 @@ namespace Novacoin
                             break;
                         }
                     }
-                    else if (opcode1 != opcode2 || args1.SequenceEqual(args2))
+                    else if (opcode1 != opcode2 || !args1.SequenceEqual(args2))
                     {
                         // Others must match exactly
                         break;
@@ -761,5 +850,85 @@ 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);
+        }
+
     };
 }