2

What would be the easiest way to retrieve addresses that are used in specific transaction? For instance, if you look at following transaction which is retrieved by getblock of JSON RPC with argument of verbosity 2 (https://developer.bitcoin.org/reference/rpc/getblock.html),

....
"tx":[
      {
         .....
         "vin":[
            {
               "coinbase":....,
               "sequence": ...
            }
         ],
         "vout":[
            {
               "value":...,
               "n":...,
               "scriptPubKey":{
                  "asm":...,
                  "desc":...,
                  "hex":...,
                  "type":...
               }
            }
]

How can I know receiver's address by looking at "scriptPubKey"? One of the challenges was there were different types of scriptPubKey so that it was kind of hard to parse them. Does anyone know best way to retrieve public address from any kind of scriptPubKey, so that the result will be matched with the addresses that are used by block explorers?

P.S. I think some of the newer transactions actually contain "Address" field inside the scriptPubKey, but older transactions do not. I want to have a generalized method that can handle any kind of transactions.

8
  • 2
    Do you mean a way to decode scriptPubKeys yourself from hex data to addresses, or a way to get it from the Bitcoin Core RPC output? Because if an output has an address, the RPC output will list it. But addresses only exist for scriptPubKeys that match certain patterns; others just don't have one. Mar 1, 2023 at 3:37
  • I was referring to a way to decode scriptPubKeys to addresses. For example, if you retrieve block height 13367 from RPC, it won't give you address explicitly even though the type is pubkey. However, if you use blockchain.info API, it contains address: blockchain.info/rawblock/… I am wondering how I can do this without API's help.
    – xor
    Mar 1, 2023 at 20:56
  • Also, do you mind if you can explain what you meant by address does not exist for certain types of transactions?
    – xor
    Mar 1, 2023 at 20:58
  • That's because blockchain.info is using an outdated notion of "address". Pay-to-pubkey outputs don't have addresses corresponding to them, and never had any (it's not possible to pay to such outputs by paying to that address). However, historically, at some point they were lumped together with P2PKH (pay to pubkey hash) outputs for the same public key (which do have addresses). This is currently considered a historical mistake, as it was confusing addresses (a way to specify who to pay) with identities (which public key is involved). Since the introduction of multisig, these are distinct. Mar 1, 2023 at 20:58
  • Addresses don't exist at the Bitcoin protocol level; only scripts exists. Some scripts are very common, so human-readable ways of communicating them were introduced, known as addresses. Nothing in the protocol however restricts people from creating payments to scripts which don't have a corresponding address. One prominent example is pay-to-pubkey (P2PK)... no address was ever defined for such outputs, but they were at some point common on chain, so explorers lumped them together with another type of script (which does have an address) for the same key. Mar 1, 2023 at 21:02

1 Answer 1

4

In short, the answer to your question depends on what you mean by "address". Historically, there are two different concepts with that name which I'll refer to as payment destinations and public key identifiers:

  • If you want payment destinations, these are already reported in the Bitcoin Core RPC output in the "address" field.
  • If you want the dated concept of public key identifiers as some block explorers still use, my hope is to convince you that this is terrible practice that you shouldn't try to imitate or persist at all.

These aren't well-established names, nor are they concepts I want to introduce a name for. They're just what I use in this answer to distinguish the two.


For background: Bitcoin transactions pay to scriptPubKeys, these are scripts that encode the conditions under which the sent coins can be spent. When a receiver R expects a sender S to pay them, they need some way of communicating to S ('s software) what scriptPubKey they expect S to construct so that S can access them.

Early in Bitcoin's history (before 2012) there were only two common types of scriptPubKeys, pay-to-pubkey (P2PK; where the scriptPubKey directly stores the public key of the recipient) and pay-to-pubkey-hash (P2PKH; where the scriptPubKey only stores a hash of the public key of the recipient). However, only a human-readable notation was introduced for P2PKH. P2PK was still in use, e.g. in mining software where no script needed to be communicated to any other party (it was just miners paying themselves effectively), but any "payment" use that involves one party asking to be paid by another party used P2PKH.

However, despite there not existing a way to instruct someone to pay using P2PK (=payment destination), people still needed a way to describe who/what a payment was for (=public key identifier). For listing who a P2PK output was for, the choice was obvious: just use the address for the corresponding P2PKH payment destination.

Clearly, a payment to a P2PK scriptPubKey with key P and a payment to a P2PKH scriptPubKey with key hash equal to the hash of P are spendable by the same person, so as a way to determine who a payment is for (=public key identification), this approach works. However, it does create a confusion of the form that someone might see a P2PK output, compute its corresponding P2PKH address, and then create another payment to that same address (=payment destination), and perhaps expect to see the same scriptPubKey appear.

Now, P2PK and P2PKH scripts are not the only types of scripts that Bitcoin supports (even from the very beginning). Another possible script is bare multisig: a scriptPubKey that contains multiple people's public keys directly, so everyone can see who it is for. This saw some limited use, and some infrastructure was introduced to make this work, including reporting these types of outputs as having multiple addresses in the public key identification meaning: one address for each of the public keys involved. Bitcoin Core's RPC output in fact worked this way for a while; there was no "address" field, but an "addresses" field, which could contain multiple elements. For bare multisig scriptPubKeys, it'd list one address for each public key. For P2PK and P2PKH scriptPubKeys, it'd just list one address (the one corresponding to the public key's P2PKH payment destination).

However, no address (in the payment destination sense) type was ever defined for bare multisig, so there was no way to instruct anyone to create such payments. Instead, in 2012, following the P2SH softfork, a very different payment destination address format was introduced: one where the receiver only reveals a hash of a script to the sender, and the individual public key(s) are not revealed to the network at sending time. The public key identification interpretation of addresses fails completely here. We have a new payment destination address type from which nothing about the participating public key(s) can be inferred at all.

And this concept caught on: P2SH (and its successors) actually ended up widely adopted, and bare multisig pretty much died. There were good reasons for this:

  • more private due to not needing to tell the sender the public keys about all the participants in the multisig scheme (which is none of the senders' business)
  • cheaper for the sender (as they don't need to encode all those public keys on chain in their transaction), moving the cost of using complex logic to the receiver, who is the party that decided to use the complexity
  • much more convenient (a single payment destination string, rather than some possible complex structure containing multiple public keys)

After P2SH, even newer script extensions were added, with corresponding address (=payment destination) encodings. The table below hopefully summarizes them:

scriptPubKey type Introduced Use as payment destination Use as public key identifier
P2PKH 2009 Legacy base58check format Yes, contains public key hash
Bare multisig 2009 - Sort-of, with multiple "addresses" per output
P2PK 2009 - Yes, public key hash can be computed from public key.
P2SH 2012 BIP13 base58check format -
P2WPKH 2017 BIP173 bech32 format Yes in theory, contains public key hash, but not commonly done.
P2WSH 2017 BIP173 bech32 format -
P2TR 2020 BIP350 bech32m format -

So overall:

  • Bitcoin transaction outputs contain scriptPubKeys, which can be anything, and are not restricted to any particular set of standard scripts.
  • Some types of scriptPubKeys have an associated address format that can be used to instruct parties to pay to that scriptPubKey.
  • Some types of scriptPubKey allow determining which public key (hash) the payment is for, but that's increasingly not the case for modern scripts.
  • Historically, the P2PKH address format was used as a way to represent "who" a scriptPubKey is for, as opposed to encoding a specific scriptPubKey (which involved representing P2PK using the P2PKH addresss format, and bare multisig even as multiple P2PKH addresses). This doesn't make sense anymore: the introduction of P2SH and its successors show that in general, one cannot identify who from a scriptPubKeys, and doing this just reinforces a misunderstanding.
8
  • This is a thing of beauty. Thank you so much. Most of them make sense now, but I am still not sure how block explorers generate addresses for all kind of transactions then. Even though they are using an old notion of "address" (payment destination), they are able to accumulate balance with unique addresses for all kinds of transaction, right? I guess I want to understand how can people assign unique address to account by just looking at the chain data, like block explorers are doing.
    – xor
    Mar 1, 2023 at 22:59
  • I apologize if this is a stupid question or if you already explained it in the above post, but I need to clarify my understanding. Now I think I understand what you meant by "payment destination" and "public key identifier" since the transaction is paying to "script", and sometimes the requirement to unlock that script is more complexed than represented with one public key. However, I need to find a way to represent a unique representation for payment destination.
    – xor
    Mar 16, 2023 at 14:07
  • @xor If there is no well-defined address for a scriptPubKey (in the payment destination sense), you're out of luck. If you want to be unambiguous, you could just hex encode the scriptPubKey or so, but there is no way that's both unambiguous and well-established. Certain block explorers just don't show anything at all if there is no address (including for P2PK outputs, e.g. blockstream.info/nojs/tx/…). Mar 16, 2023 at 14:12
  • If my understanding is correct, all of the scriptPubKey types that can be used as "payment destination" has an "address" field when retrieved with JSON-RPC. Is my understanding correct? Secondly, if I want to derive a payment destination for the "P2PK" type, if I just hash the public key and use legacy base58check format, will I get the same "address" as the "P2PKH" when the public key is the same? Finally, even though the transaction is paid to the same person in "P2PKH" and "P2WPKH" (meaning that a private key owner can spend both outputs), address fields will have different values, right?
    – xor
    Mar 16, 2023 at 14:14
  • If an an address exists for a scriptPubKey in the payment destination sense (as in: sending to that address will result in the same scriptPubKey), then the JSON-RPC interface will report that address. P2PK scriptPubKeys don't have such an address. You can in theory take the public key in a P2PK output and hash it, and encode that as a P2PKH address, but that's only meaningful if your goal is identifying public keys, because sending to that P2PKH address will not send to the same scriptPubKey, and possibly even burn funds. Further, for e.g. P2SH/P2WSH/P2TR no such hack is possible. Mar 16, 2023 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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