Renaming ScriptOpcode to ScriptCode, addition of script Solver functionality.
authorCryptoManiac <balthazar.ad@gmail.com>
Thu, 20 Aug 2015 15:11:56 +0000 (18:11 +0300)
committerCryptoManiac <balthazar.ad@gmail.com>
Thu, 20 Aug 2015 15:11:56 +0000 (18:11 +0300)
Novacoin/CScript.cs
Novacoin/Novacoin.csproj
Novacoin/ScriptCode.cs [moved from Novacoin/ScriptOpcode.cs with 73% similarity]

index f77b8c8..c25703c 100644 (file)
@@ -183,7 +183,7 @@ namespace Novacoin
             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
             
             // Scan opcodes sequence
-            while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+            while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
             {
                 if (opcode > opcodetype.OP_16)
                 {
@@ -207,7 +207,7 @@ namespace Novacoin
             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
 
             // Scan opcodes sequence
-            while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+            while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
             {
                 byte[] data = pushArgs.ToArray();
 
@@ -242,13 +242,32 @@ namespace Novacoin
         /// <returns>Checking result</returns>
         public bool IsPayToScriptHash()
         {
+            // Sender provides redeem script hash, receiver provides signature list and redeem script
+            // OP_HASH160 20 [20 byte hash] OP_EQUAL
             return (codeBytes.Count() == 23 &&
                     codeBytes[0] == (byte)opcodetype.OP_HASH160 &&
-                    codeBytes[1] == 0x14 &&
+                    codeBytes[1] == 0x14 && // 20 bytes hash length prefix
                     codeBytes[22] == (byte)opcodetype.OP_EQUAL);
         }
 
         /// <summary>
+        /// Quick test for pay-to-pubkeyhash CScripts
+        /// </summary>
+        /// <returns>Checking result</returns>
+        public bool IsPayToPubKeyHash()
+        {
+            // Sender provides hash of pubkey, receiver provides signature and pubkey
+            // OP_DUP OP_HASH160 20 [20 byte hash] OP_EQUALVERIFY OP_CHECKSIG
+
+            return (codeBytes.Count == 25 &&
+                    codeBytes[0] == (byte) opcodetype.OP_DUP &&
+                    codeBytes[1] == (byte)opcodetype.OP_HASH160 &&
+                    codeBytes[2] == 0x14 && // 20 bytes hash length prefix
+                    codeBytes[23] == (byte)opcodetype.OP_EQUALVERIFY &&
+                    codeBytes[24] == (byte)opcodetype.OP_CHECKSIG);
+        }
+
+        /// <summary>
         /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
         /// as 20 sigops. With pay-to-script-hash, that changed:
         /// CHECKMULTISIGs serialized in scriptSigs are
@@ -268,7 +287,7 @@ namespace Novacoin
             opcodetype lastOpcode = opcodetype.OP_INVALIDOPCODE;
 
             // Scan opcodes sequence
-            while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+            while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
             {
                 if (opcode == opcodetype.OP_CHECKSIG || opcode == opcodetype.OP_CHECKSIGVERIFY)
                 {
@@ -278,7 +297,7 @@ namespace Novacoin
                 {
                     if (fAccurate && lastOpcode >= opcodetype.OP_1 && lastOpcode <= opcodetype.OP_16)
                     {
-                        nCount += ScriptOpcode.DecodeOP_N(lastOpcode);
+                        nCount += ScriptCode.DecodeOP_N(lastOpcode);
                     }
                     else
                     {
@@ -311,7 +330,7 @@ namespace Novacoin
             opcodetype opcode; // Current opcode
             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
 
-            while (ScriptOpcode.GetOp(ref wScriptSig, out opcode, out pushArgs))
+            while (ScriptCode.GetOp(ref wScriptSig, out opcode, out pushArgs))
             {
                 if (opcode > opcodetype.OP_16)
                 {
@@ -346,17 +365,25 @@ namespace Novacoin
         public void SetMultiSig(int nRequired, IEnumerable<CPubKey> keys)
         {
             codeBytes.Clear();
-            AddOp(ScriptOpcode.EncodeOP_N(nRequired));
+            AddOp(ScriptCode.EncodeOP_N(nRequired));
 
             foreach (CPubKey key in keys)
             {
                 PushData(key.Public.ToList());
             }
-            AddOp(ScriptOpcode.EncodeOP_N(keys.Count()));
+            AddOp(ScriptCode.EncodeOP_N(keys.Count()));
             AddOp(opcodetype.OP_CHECKMULTISIG);
         }
 
         /// <summary>
+        /// Access to scrypt code.
+        /// </summary>
+        public IEnumerable<byte> Enumerable
+        {
+            get { return codeBytes; }
+        }
+
+        /// <summary>
         /// Disassemble current script code
         /// </summary>
         /// <returns>Code listing</returns>
@@ -367,7 +394,7 @@ namespace Novacoin
 
             opcodetype opcode; // Current opcode
             IEnumerable<byte> pushArgs; // OP_PUSHDATAn argument
-            while (ScriptOpcode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
+            while (ScriptCode.GetOp(ref wCodeBytes, out opcode, out pushArgs))
             {
                 if (sb.Length != 0)
                 {
@@ -376,11 +403,11 @@ namespace Novacoin
 
                 if (0 <= opcode && opcode <= opcodetype.OP_PUSHDATA4)
                 {
-                    sb.Append(ScriptOpcode.ValueString(pushArgs));
+                    sb.Append(ScriptCode.ValueString(pushArgs));
                 }
                 else
                 {
-                    sb.Append(ScriptOpcode.GetOpName(opcode));
+                    sb.Append(ScriptCode.GetOpName(opcode));
                 }
             }
 
index f02b222..6f13cc7 100644 (file)
@@ -60,7 +60,7 @@
     <Compile Include="CBlockHeader.cs" />
     <Compile Include="CBlock.cs" />
     <Compile Include="CScript.cs" />
-    <Compile Include="ScriptOpcode.cs" />
+    <Compile Include="ScriptCode.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
similarity index 73%
rename from Novacoin/ScriptOpcode.cs
rename to Novacoin/ScriptCode.cs
index 436de24..5001a3b 100644 (file)
@@ -152,7 +152,22 @@ namespace Novacoin
         OP_INVALIDOPCODE = 0xff,
     };
 
-    public static class ScriptOpcode
+    /// <summary>
+    /// Transaction output types.
+    /// </summary>
+    public enum txnouttype
+    {
+        TX_NONSTANDARD,
+
+        // 'standard' transaction types:
+        TX_PUBKEY,
+        TX_PUBKEYHASH,
+        TX_SCRIPTHASH,
+        TX_MULTISIG,
+        TX_NULL_DATA,
+    };
+
+    public static class ScriptCode
     {
         
         /// <summary>
@@ -575,6 +590,176 @@ namespace Novacoin
             return (opcodetype)(opcodetype.OP_1 + n - 1);
         }
 
+        public static bool Solver(CScript scriptPubKey, out txnouttype typeRet, out IList<IEnumerable<byte>> solutions)
+        {
+            solutions = new List<IEnumerable<byte>>();
+
+            // 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())
+            {
+                typeRet = txnouttype.TX_SCRIPTHASH;
+
+                // Take 20 bytes with offset of 2 bytes
+                IEnumerable<byte> hashBytes = scriptPubKey.Enumerable.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())
+            {
+                typeRet = txnouttype.TX_PUBKEYHASH;
+
+                // Take 20 bytes with offset of 3 bytes
+                IEnumerable<byte> hashBytes = scriptPubKey.Enumerable.Skip(3).Take(20);
+                solutions.Add(hashBytes);
+
+                return true;
+            }
+
+            List<Tuple<txnouttype, IEnumerable<byte>>> templateTuples = new List<Tuple<txnouttype, IEnumerable<byte>>>();
+
+            // Sender provides pubkey, receiver adds signature
+            // [ECDSA public key] OP_CHECKSIG
+            templateTuples.Add(
+                new Tuple<txnouttype, IEnumerable<byte>>(
+                    txnouttype.TX_PUBKEY, 
+                    new byte[] { (byte)opcodetype.OP_PUBKEY, (byte)opcodetype.OP_CHECKSIG })
+            );
+
+            // Sender provides N pubkeys, receivers provides M signatures
+            // N [pubkey1] [pubkey2] ... [pubkeyN] M OP_CHECKMULTISIG
+            // Where N and M are small integer opcodes (OP1 ... OP_16)
+            templateTuples.Add(
+                new Tuple<txnouttype, IEnumerable<byte>>(
+                    txnouttype.TX_MULTISIG,
+                    new byte[] { (byte)opcodetype.OP_SMALLINTEGER, (byte)opcodetype.OP_PUBKEYS, (byte)opcodetype.OP_SMALLINTEGER, (byte)opcodetype.OP_CHECKMULTISIG })
+            );
+
+            // Data-carrying output
+            // OP_RETURN [up to 80 bytes of data]
+            templateTuples.Add(
+                new Tuple<txnouttype, IEnumerable<byte>>(
+                    txnouttype.TX_NULL_DATA, 
+                    new byte[] { (byte)opcodetype.OP_RETURN, (byte)opcodetype.OP_SMALLDATA })
+            );
+
+            // Nonstandard tx output
+            typeRet = txnouttype.TX_NONSTANDARD;
+
+            foreach (Tuple<txnouttype, IEnumerable<byte>> templateTuple in templateTuples)
+            {
+                CScript script2 = new CScript(templateTuple.Item2);
+                CScript script1 = scriptPubKey;
+
+                opcodetype opcode1, opcode2;
+
+                // Compare
+                WrappedList<byte> wl1 = script1.GetWrappedList();
+                WrappedList<byte> wl2 = script2.GetWrappedList();
+
+                IEnumerable<byte> args1, args2;
+
+                byte last1 = script1.Enumerable.Last();
+                byte last2 = script2.Enumerable.Last();
+
+                while (true)
+                {
+                    if (wl1.GetItem() == last1 && wl2.GetItem() == last2)
+                    {
+                        // Found a match
+                        typeRet = templateTuple.Item1;
+                        if (typeRet == txnouttype.TX_MULTISIG)
+                        {
+                            // Additional checks for TX_MULTISIG:
+                            byte m = solutions.First().First();
+                            byte n = solutions.Last().First();
+
+                            if (m < 1 || n < 1 || m > n || solutions.Count - 2 != n)
+                            {
+                                return false;
+                            }
+                        }
+                        return true;
+                    }
+
+                    if (!GetOp(ref wl1, out opcode1, out args1))
+                    {
+                        break;
+                    }
+                    if (!GetOp(ref wl2, out opcode2, out args2))
+                    {
+                        break;
+                    }
+
+                    // Template matching opcodes:
+                    if (opcode2 == opcodetype.OP_PUBKEYS)
+                    {
+                        while (args1.Count() >= 33 && args1.Count() <= 120)
+                        {
+                            solutions.Add(args1);
+                            if (!GetOp(ref wl1, out opcode1, out args1))
+                            {
+                                break;
+                            }
+                        }
+                        if (!GetOp(ref wl2, out opcode2, out args2))
+                            break;
+                        // Normal situation is to fall through
+                        // to other if/else statements
+                    }
+                    if (opcode2 == opcodetype.OP_PUBKEY)
+                    {
+                        if (args1.Count() < 33 || args1.Count() > 120)
+                        {
+                            break;
+                        }
+                        solutions.Add(args1);
+                    }
+                    else if (opcode2 == opcodetype.OP_PUBKEYHASH)
+                    {
+                        if (args1.Count() != 20) // hash160 size
+                        {
+                            break;
+                        }
+                        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))
+                        {
+                            byte n = (byte)DecodeOP_N(opcode1);
+                            solutions.Add(new byte[] { n });
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                    else if (opcode2 == opcodetype.OP_SMALLDATA)
+                    {
+                        // small pushdata, <= 80 bytes
+                        if (args1.Count() > 80)
+                        {
+                            break;
+                        }
+                    }
+                    else if (opcode1 != opcode2 || args1.SequenceEqual(args2))
+                    {
+                        // Others must match exactly
+                        break;
+                    }
+                }
+            }
+
+            solutions.Clear();
+            typeRet = txnouttype.TX_NONSTANDARD;
 
+            return false;
+        }
     };
 }