2

I use zeroMQ to get the transactions from Bitcoin Core. The output looks like this:

{ value: 200000,
  script: <Buffer 00 20 e3 30 7e 8e 0b 63 0e 53 c2 f1 f8 17 06 7b ce 01 c4 38 92 83 3e 02 2d ad f5 e9 5f 31 be 89 b1 22>
}

The same output looks like this in Bitcoin Core:

{
      "value": 0.00200000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 e3307e8e0b630e53c2f1f817067bce01c43892833e022dadf5e95f31be89b122",
        "hex": "0020e3307e8e0b630e53c2f1f817067bce01c43892833e022dadf5e95f31be89b122",
        "reqSigs": 1,
        "type": "witness_v0_scripthash",
        "addresses": [
          "tb1quvc8arstvv898sh3lqtsv77wq8zr3y5r8cpzmt04a90nr05fky3qemqr3j"
        ]
      }
    }

Question: How do I get the scriptPubKey type from the script buffer ?

1

Internally, Bitcoin does not have the concept of scriptPubKey type. To the system, scriptPubKey is simply a mathematical equation into which some bitcoins have been locked (UTXO) and the spender must satisfy the unlocking conditions, which most of the times are the signature(s) and public key(s), in order to spend those bitcoins. These basic construct can be used to build higher level concepts like the scriptPubKey type that you are referring in your question.

Since Bitcoin is a programmable money, a user can lock their bitcoins in a number of different ways. The Bitcoin community has accepted certain output types as standard locking scripts. Below are some of the standard output scripts (scriptPubKey) that can be used to determine the script address type (main net address types have been used):

  1. Pay to Public Key Hash (P2PKH): In this type of script, the bitcoins are locked in the hash of a public key. The output script will look like the following:
    {
      "value": 0.00200000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 243f1394f44554f4ce3fd68649c19adc483ce924 OP_EQUALVERIFY OP_CHECKSIG"
        "hex": "76a914243f1394f44554f4ce3fd68649c19adc483ce92488ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "14JetYAhLevTRaePcAAX1vRMwaJt2QGRzp"
        ]
      }
    }
    
  2. Pay to Script Hash (P2SH): In this type of script, the bitcoins are locked in the hash of script. The script can be a multi-sig output, time-locked output, P2SH-P2WPKH (SegWit address in P2SH format) and others. The output script looks like the following:
    {
      "value": 0.00200000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_HASH160 243f1394f44554f4ce3fd68649c19adc483ce924 OP_EQUAL"
        "hex": "a914243f1394f44554f4ce3fd68649c19adc483ce92487",
        "reqSigs": 1,
        "type": "scripthash",
        "addresses": [
          "34zfp5f8tZEqWkLpjFq7SYnJ66bbdaTmt"
        ]
      }
    }
    
  3. Pay to Witness Public Key Hash (P2WPKH): In this script, the bitcoins are locked in the hash of the public key, but follows SegWit. The leading 00 indicate that it is SegWit version 0. The output script looks like the following:
    {
      "value": 0.00200000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 eb308eb99tb8ff3797d2e5cfb33a9231700d6195"
        "hex": "0014eb308eb99tb8ff3797d2e5cfb33a9231700d6195",
        "reqSigs": 1,
        "type": "witness_v0_keyhash",
        "addresses": [
          "bc1qavcgawvmerln097juh8mxw5jx9cq6cv46xmzxk"
        ]
      }
    }
    
  4. Pay to Witness Script Hash (P2WSH): In this script, the bitcoins are locked in the hash of a script, but follows SegWit. Similar to P2SH, the redeemScript can be a multi-sig output, time-locked output, etc. The leading 00 byte indicate that it is SegWit version 0. Below is how the locking script looks like (your example in the question):
    {
      "value": 0.00200000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 e3307e8e0b630e53c2f1f817067bce01c43892833e022dadf5e95f31be89b122"
        "hex": "0020e3307e8e0b630e53c2f1f817067bce01c43892833e022dadf5e95f31be89b122",
        "reqSigs": 1,
        "type": "witness_v0_scripthash",
        "addresses": [
          "bc1quvc8arstvv898sh3lqtsv77wq8zr3y5r8cpzmt04a90nr05fky3qwnkvta"
        ]
      }
    }
    
  5. Pay to Public Key (P2PK): This type of output script pays the public key directly. Note: Although currently Bitcoin Core shows an address starting with 1 for P2PK, it is an error that will be rectified in the next release of Bitcoin-Core. Below is how the output script looks like:
    {
      "value": 0.00200000,
      "n": 0,
      "scriptPubKey": {
        "asm": "02d2a6892c51741f83759088d9eed12c2c52eea9b999ecc36af1cb569b733b6ff1 OP_CHECKSIG"
        "hex": "2002d2a6892c51741f83759088d9eed12c2c52eea9b999ecc36af1cb569b733b6ff1ac",
        "reqSigs": 1,
        "type": "pubkey",
        "addresses": [
          "1FK1FmLuTY19JGdb9SsKHs68HxRD69jtc1"
        ]
      }
    }
    

As I said earlier, bitcoins can be locked in any allowed operator operations. However, if the output script does not belong to the above five types, the output script will be non-standard and you will see the type as "nonstandard" in the scriptPubKey field.

  • So, If I understand well, in your first example: 76 -> OP_DUP a9 -> OP_HASH160 14 data to be pushed onto the stack ( 0x14 = 20 bytes = 40 chars) -> 243f1394f44554f4ce3fd68649c19adc483ce924 88 -> OP_EQUALVERIFY ac -> OP_CHECKSIG from this pattern we can conclude the scriptPubKey type is pubkeyhash ? – jfjobidon Oct 17 at 19:49
  • @jfjobidon Yep, absolutely correct. – Ugam Kamat Oct 17 at 20:32
1

You need to parse the script and get to the script type by examining the opcodes.

If you examine the rpc/rawtransaction.cpp source file on Bitcoin Core you'll see the RPC call that outputs this is getrawtransaction(). Tracing it further it leads us to ScriptPubKeyToUniv in core_write.cpp which calls so let's take a look at that:

const char* GetTxnOutputType(txnouttype t)
{
    switch (t)
    {
    case TX_NONSTANDARD: return "nonstandard";
    case TX_PUBKEY: return "pubkey";
    case TX_PUBKEYHASH: return "pubkeyhash";
    case TX_SCRIPTHASH: return "scripthash";
    case TX_MULTISIG: return "multisig";
    case TX_NULL_DATA: return "nulldata";
    case TX_WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
    case TX_WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
    case TX_WITNESS_UNKNOWN: return "witness_unknown";
    }
    return nullptr;
}

This subroutine maps script types to the string name which you asked about. In your case you got a TX_WITNESS_V0_SCRIPTHASH whose value is 0x06 per its definition in script/standard.h :

enum txnouttype
{
    TX_NONSTANDARD,
    // 'standard' transaction types:
    TX_PUBKEY,
    TX_PUBKEYHASH,
    TX_SCRIPTHASH,
    TX_MULTISIG,
    TX_NULL_DATA, //!< unspendable OP_RETURN script that carries data
    TX_WITNESS_V0_SCRIPTHASH,
    TX_WITNESS_V0_KEYHASH,
    TX_WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
};

We now know how to extract the scriptPubKey type name from the value, but we don't know how to extract that value from the raw bytes, so let's take a look at that next.

txnouttype is extracted from scriptPubkey by ExtractDestinations which is defined in script/standard.cpp, the relevant line being typeRet = Solver(scriptPubKey, vSolutions);. So let's step into Solver.

Here's the answer to your question:

txnouttype Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned char>>& vSolutionsRet)
{
    vSolutionsRet.clear();

    // Shortcut for pay-to-script-hash, which are more constrained than the other types:
    // it is always OP_HASH160 20 [20 byte hash] OP_EQUAL
    if (scriptPubKey.IsPayToScriptHash())
    {
        std::vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22);
        vSolutionsRet.push_back(hashBytes);
        return TX_SCRIPTHASH;
    }

    int witnessversion;
    std::vector<unsigned char> witnessprogram;
    if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
        if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) {
            vSolutionsRet.push_back(witnessprogram);
            return TX_WITNESS_V0_KEYHASH;
        }
        if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
            vSolutionsRet.push_back(witnessprogram);
            return TX_WITNESS_V0_SCRIPTHASH;
        }
        if (witnessversion != 0) {
            vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
            vSolutionsRet.push_back(std::move(witnessprogram));
            return TX_WITNESS_UNKNOWN;
        }
        return TX_NONSTANDARD;
    }

    // Provably prunable, data-carrying output
    //
    // So long as script passes the IsUnspendable() test and all but the first
    // byte passes the IsPushOnly() test we don't care what exactly is in the
    // script.
    if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
        return TX_NULL_DATA;
    }

    std::vector<unsigned char> data;
    if (MatchPayToPubkey(scriptPubKey, data)) {
        vSolutionsRet.push_back(std::move(data));
        return TX_PUBKEY;
    }

    if (MatchPayToPubkeyHash(scriptPubKey, data)) {
        vSolutionsRet.push_back(std::move(data));
        return TX_PUBKEYHASH;
    }

    unsigned int required;
    std::vector<std::vector<unsigned char>> keys;
    if (MatchMultisig(scriptPubKey, required, keys)) {
        vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..16
        vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end());
        vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..16
        return TX_MULTISIG;
    }

    vSolutionsRet.clear();
    return TX_NONSTANDARD;
}

It returns the txnouttype and here's how it determines each one:

TX_SCRIPTHASH

this->size() == 23 &&
            (*this)[0] == OP_HASH160 &&
            (*this)[1] == 0x14 &&
            (*this)[22] == OP_EQUAL

If the script size is 23 bytes, the first script byte is OP_HASH160, second byte is 0x14 and byte 22 is OP_EQUAL then it's a TX_SCRIPTHASH.

Witness Program

The following tests if the script is a witness program:

this->size() == 34 &&
            (*this)[0] == OP_0 &&
            (*this)[1] == 0x20

So, if the script is 34 bytes long, the first OP is 0 and the second byte is 0x20 then it's a witness program (This is the case in your question.).

If it's a witness program, then there are 4 possible script types:

First Witness Script type: TX_WITNESS_V0_KEYHASH

if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) {
            vSolutionsRet.push_back(witnessprogram);
            return TX_WITNESS_V0_KEYHASH;
        }

If witness version is zero and the witness program is WITNESS_V0_KEYHASH_SIZE (20 bytes) long then it's a TX_WITNESS_V0_KEYHASH.

Second Witness Script type: TX_WITNESS_V0_SCRIPTHASH

If witness version is zer and the witness program is WITNESS_V0_SCRIPTHASH_SIZE (32 bytes) long then it's a TX_WITNESS_V0_SCRIPTHASH. This is the case in your question.

Note: We now know the specific answer to your question : you know if it's a witness_v0_scripthash if the script is 34 bytes long, begins with 00 20 and then there are 32 bytes following it.

Let's proceed for completeness.

Third Witness Script type: TX_WITNESS_UNKNOWN

If the witness version is different from zero, it's unknown.

Fourth and last Witness script type is TX_NONSTANDARD, which means it's not recognized by Bitcon Core.

Now onto the other script types.

TX_NULL_DATA

If the scriptPubKey size is more than one byte long and the first OP is a RETURN, then everything that follows is NULL data.

TX_PUBKEY

If the script is CPubKey::PUBLIC_KEY_SIZE (65) bytes long and the first byte on the script is 65 (CPubKey::PUBLIC_KEY_SIZE) and if the last OP on the script is OP_CHECKSIG then it's a legacy TX_PUBKEY script.

TX_PUBKEYHASH

If the script is 25 bytes long (this length is harcoded in script/standard.cpp), the first operation is OP_DUP, the second operation is OP_HASH160 and script[23] is OP_EQUALVERIFY and script[24] is OP_CHECKSIG then it's the most common type of script of all: TX_PUBKEYHASH.

TX_MULTISIG

Multisig is solved by MatchMultisig in script/standard.cpp. What it does is parse the script looping through it and calling GetOp (script/script.h) to parse the sig out and CPubKey::ValidSize(data) which checks if each signature is the right size. If at the end of this check we are at the end of the script, verified by (it + 1 == script.end()), then it's a valid multisig script.

TX_NONSTANDARD

Lastly if none of the above rules match, then it's a nonstandard script containing undefined contents. Most nodes would probably reject this script but miners and pools are able to insert stuff here if they want to.

These are all the script types in Bitcoin. The rules presented for each one is how you get the type out of scriptPubKey. To turn it into a string you use the enum map in GetTxnOutputType.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.