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:
||Use as payment destination
||Use as public key identifier
||Legacy base58check format
||Yes, contains public key hash
||Sort-of, with multiple "addresses" per output
||Yes, public key hash can be computed from public key.
||BIP13 base58check format
||BIP173 bech32 format
||Yes in theory, contains public key hash, but not commonly done.
||BIP173 bech32 format
||BIP350 bech32m format
- 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.