/// <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<byte[]> solutions) { byte[] scriptBytes = scriptPubKey; solutions = new List<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 var hashBytes = scriptBytes.Skip(2).Take(20); solutions.Add(hashBytes.ToArray()); 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 var hashBytes = scriptBytes.Skip(3).Take(20); solutions.Add(hashBytes.ToArray()); return true; } var templateTuples = new List<Tuple<txnouttype, byte[]>>(); // Sender provides pubkey, receiver adds signature // [ECDSA public key] OP_CHECKSIG templateTuples.Add( new Tuple<txnouttype, byte[]>( txnouttype.TX_PUBKEY, new byte[] { (byte)instruction.OP_PUBKEY, (byte)instruction.OP_CHECKSIG }) ); // Sender provides N pubkeys, receivers provides M signatures // N [pubkey1] [pubkey2] ... [pubkeyN] M OP_CHECKMULTISIG // Where N and M are small integer instructions (OP1 ... OP_16) templateTuples.Add( new Tuple<txnouttype, byte[]>( txnouttype.TX_MULTISIG, new byte[] { (byte)instruction.OP_SMALLINTEGER, (byte)instruction.OP_PUBKEYS, (byte)instruction.OP_SMALLINTEGER, (byte)instruction.OP_CHECKMULTISIG }) ); // Data-carrying output // OP_RETURN [up to 80 bytes of data] templateTuples.Add( new Tuple<txnouttype, byte[]>( txnouttype.TX_NULL_DATA, new byte[] { (byte)instruction.OP_RETURN, (byte)instruction.OP_SMALLDATA }) ); // Nonstandard tx output typeRet = txnouttype.TX_NONSTANDARD; foreach (var templateTuple in templateTuples) { var script1 = scriptPubKey; var script2 = new CScript(templateTuple.Item2); instruction opcode1, opcode2; // Compare var bq1 = script1.GetInstructionQueue(); var bq2 = script2.GetInstructionQueue(); byte[] args1, args2; int last1 = ((byte[])script1).Length - 1; int last2 = ((byte[])script2).Length - 1; while (true) { if (bq1.Index == last1 && bq2.Index == last2) { // Found a match typeRet = templateTuple.Item1; if (typeRet == txnouttype.TX_MULTISIG) { // Additional checks for TX_MULTISIG: var m = solutions.First().First(); var n = solutions.Last().First(); if (m < 1 || n < 1 || m > n || solutions.Count - 2 != n) { return false; } } return true; } if (!GetOp(ref bq1, out opcode1, out args1)) { break; } if (!GetOp(ref bq2, out opcode2, out args2)) { break; } // Template matching instructions: if (opcode2 == instruction.OP_PUBKEYS) { while (args1.Count() >= 33 && args1.Count() <= 120) { solutions.Add(args1); if (!GetOp(ref bq1, out opcode1, out args1)) { break; } } if (!GetOp(ref bq2, out opcode2, out args2)) { break; } // Normal situation is to fall through // to other if/else statements } if (opcode2 == instruction.OP_PUBKEY) { int PubKeyLen = args1.Count(); if (PubKeyLen < 33 || PubKeyLen > 120) { break; } solutions.Add(args1); } else if (opcode2 == instruction.OP_PUBKEYHASH) { if (args1.Count() != 20) // hash160 size { break; } solutions.Add(args1); } else if (opcode2 == instruction.OP_SMALLINTEGER) { // Single-byte small integer pushed onto solutions try { var n = (byte)DecodeOP_N(opcode1); solutions.Add(new byte[] { n }); } catch (Exception) { break; } } else if (opcode2 == instruction.OP_SMALLDATA) { // small pushdata, <= 80 bytes if (args1.Length > 80) { break; } } else if (opcode1 != opcode2 || !args1.SequenceEqual(args2)) { // Others must match exactly break; } } } solutions.Clear(); typeRet = txnouttype.TX_NONSTANDARD; return false; }
/// <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<byte[]> solutions; if (!Solver(scriptPubKey, out whichType, out solutions)) { // No solutions found return false; } if (whichType == txnouttype.TX_MULTISIG) { // Additional verification of OP_CHECKMULTISIG arguments var m = solutions.First()[0]; var n = solutions.Last()[0]; // 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; }
public static int ScriptSigArgsExpected(txnouttype t, IList<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().Length < 1) return -1; return solutions.First()[0] + 1; case txnouttype.TX_SCRIPTHASH: return 1; // doesn't include args needed by the script } return -1; }
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; }