user128767: I'm trying to understand how to validate the first non-coinbase transaction, that is transaction 1 of block 169, as it was validated in the old days.
This transaction has one input, which decoded ScriptSig is exactly:

PUSH (71 bytes: 70 bytes ECDSA signature ANS.1 in DER format and trailing SIGHASH_ALL byte)

This input refers to output 0 of transaction 0 of block 9, which ScriptPubKey is exactly:

PUSH (65 bytes: not-compressed flag byte and 64 public key x and y)
OP_CHECKSIG

My question is: in those old days, how did we know that we had to run a P2PKH to validate the transaction? Simply put, did we just concatenate ScriptSig and ScriptPubKey...:

PUSH (71 bytes) PUSH (65 bytes) OP_CHECKSIG

..and run the resulting script (that is: running OP_CHECKSIG as it is coded in EvalScript() in src/interpreter.cpp of the Bitcoin Core), which was supposed to be a P2PKH script, or did we create a more elaborate script from those scripts to validate?
Because nowadays, after some BIPs (witnesses, and so on), the P2PKH script looks more elaborated:

OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG

Addendum
For those like me who are interested in learning about the bitcoin by coding it from scratch and using the first blocks as a data reference, I later found this very good book. You may also find interesting to check the first Bitcoin code here.

user208: 
My question is: in those old days, how did we know that we had to run a P2PKH to validate the transaction?

We didn't, and we don't.
The validation code simply executes the scripts, instruction by instruction. The fact that we (now) call these particular scripts P2PK or P2PKH scripts is just a human designation, that determines how they get shown by wallets. But the validation code doesn't care.
Perhaps this helps: script is a programming language. Any time a transaction output was created, the wallet would construct in the transaction outputs a little program that determined how that output can be spent. Most commonly, the transaction output would be one of a very small number of specific types (e.g. P2PKH), which we've given special names. But transactions weren't, and aren't, restricted to just those two types. Scripts could use whatever opcodes they wanted, to encode whatever conditions they wanted.

Now, some things have changed since "the old days", which do factor in here:

Very early on, the scriptSig (unlocking script in the input) and the scriptPubKey (locking script in the output) were concatenated, and then executed as one, together. This is no longer the case since this was discovered to be trivially insecure, in 2010. Now the scriptSig is executed first, and then the scriptPubKey is executed separately, copying just the final state of the first as initial state for the second.
The introduction of P2SH in 2012 (BIP16) did actually introduce a special pattern that validation nodes do specifically recognize. BIP16's rule in short is "IF the scriptPubKey exactly matches this template, some additional validation rules are triggered". That's because P2SH moved the actually executed script logic from the scriptPubKey to the scriptSig, and the scriptPubKey only contains a magic hash of the real script, redirecting execution to the script in the scriptSig. So since P2SH, senders don't actually "know" what script they are sending to anymore, only its hash.
Something similar to P2SH was done again in 2017 with the introduction of segwit (BIP141).
There used to be support for pay-to-IP addresses, where your node would literally connect to that IP, ask it for a scriptPubKey to send to, and then construct a transaction that paid that did. This was very insecure (no encryption or authentication, just whatever the IP responds), and has long since been removed. Such pay-to-IP scripts were generally P2PK ones.


128767: <p>I'm trying to understand how to validate the first non-coinbase transaction, that is transaction 1 of block 169, as it was validated in the old days.</p>
<p>This transaction has one input, which decoded ScriptSig is exactly:</p>
<blockquote>
<p>PUSH (71 bytes: 70 bytes ECDSA signature ANS.1 in DER format and trailing SIGHASH_ALL byte)</p>
</blockquote>
<p>This input refers to output 0 of transaction 0 of block 9, which ScriptPubKey is exactly:</p>
<blockquote>
<p>PUSH (65 bytes: not-compressed flag byte and 64 public key x and y)
OP_CHECKSIG</p>
</blockquote>
<p>My question is: in those old days, how did we know that we had to run a P2PKH to validate the transaction? Simply put, did we just concatenate ScriptSig and ScriptPubKey...:</p>
<blockquote>
<p>PUSH (71 bytes) PUSH (65 bytes) OP_CHECKSIG</p>
</blockquote>
<p>..and run the resulting script (that is: running OP_CHECKSIG as it is coded in EvalScript() in src/interpreter.cpp of the Bitcoin Core), which was supposed to be a P2PKH script, or did we create a more elaborate script from those scripts to validate?</p>
<p>Because nowadays, after some BIPs (witnesses, and so on), the P2PKH script looks more elaborated:</p>
<blockquote>
<p>OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG</p>
</blockquote>
<p><strong>Addendum</strong></p>
<p>For those like me who are interested in learning about the bitcoin by coding it from scratch and using the first blocks as a data reference, I later found <a href="https://www.oreilly.com/library/view/programming-bitcoin/9781492031482/" rel="nofollow noreferrer">this very good book</a>. You may also find interesting to check the first Bitcoin code <a href="https://satoshi.nakamotoinstitute.org/code/" rel="nofollow noreferrer">here</a>.</p>
 208: <blockquote>
<p>My question is: in those old days, how did we know that we had to run a P2PKH to validate the transaction?</p>
</blockquote>
<p>We didn't, and we don't.</p>
<p>The validation code simply executes the scripts, instruction by instruction. The fact that we (now) call these particular scripts P2PK or P2PKH scripts is just a human designation, that determines how they get shown by wallets. But the validation code doesn't care.</p>
<p>Perhaps this helps: script is a programming language. Any time a transaction output was created, the wallet would construct in the transaction outputs a little program that determined how that output can be spent. Most commonly, the transaction output would be one of a very small number of specific types (e.g. P2PKH), which we've given special names. But transactions weren't, and aren't, restricted to just those two types. Scripts could use whatever opcodes they wanted, to encode whatever conditions they wanted.</p>
<hr />
<p>Now, some things have changed since &quot;the old days&quot;, which do factor in here:</p>
<ul>
<li>Very early on, the scriptSig (unlocking script in the input) and the scriptPubKey (locking script in the output) were concatenated, and then executed as one, together. This is no longer the case since this was discovered to be trivially insecure, in 2010. Now the scriptSig is executed first, and then the scriptPubKey is executed separately, copying just the final state of the first as initial state for the second.</li>
<li>The introduction of P2SH in 2012 (BIP16) <strong>did</strong> actually introduce a special pattern that validation nodes do specifically recognize. BIP16's rule in short is &quot;IF the scriptPubKey exactly matches this template, some additional validation rules are triggered&quot;. That's because P2SH moved the actually executed script logic from the scriptPubKey to the scriptSig, and the scriptPubKey only contains a magic hash of the real script, redirecting execution to the script in the scriptSig. So since P2SH, senders don't actually &quot;know&quot; what script they are sending to anymore, only its hash.</li>
<li>Something similar to P2SH was done again in 2017 with the introduction of segwit (BIP141).</li>
<li>There used to be support for pay-to-IP addresses, where your node would literally connect to that IP, ask it for a scriptPubKey to send to, and then construct a transaction that paid that did. This was very insecure (no encryption or authentication, just whatever the IP responds), and has long since been removed. Such pay-to-IP scripts were generally P2PK ones.</li>
</ul>
