Script Validation (P2PKH-lite)
Script Validation (P2PKH-lite)
You are the transaction gatekeeper for a toy Bitcoin-like node.
A transaction input is valid only if its unlocking script (scriptSig) and locking script (scriptPubKey) execute successfully on a shared stack.
Implement a minimal script engine for P2PKH-like validation using HASH256 (double SHA-256) and P-256 ECDSA.
Script encoding
Scripts are byte slices containing either pushdata or opcodes.
Pushdata
- A byte
nwith1 <= n <= 75means: push nextnbytes. - If fewer than
nbytes remain, script is malformed -> fail.
Opcodes (single byte)
0x76OP_DUP0xAAOP_HASH2560x88OP_EQUALVERIFY0xACOP_CHECKSIG
Any other opcode is invalid -> fail.
Simplified signature hash
For this problem:
S = SHA256(SHA256(tx.ID || uint32_le(inputIndex) || scriptPubKey))
All concatenations are raw byte concatenations.
inputIndex must be encoded as 4-byte little-endian.
Types and function
type Outpoint struct {
TxID [32]byte
Index uint32
}
type TxIn struct {
Prev Outpoint
ScriptSig []byte
}
type TxOut struct {
Value uint64
ScriptPubKey []byte
}
type Transaction struct {
ID [32]byte
Inputs []TxIn
Outputs []TxOut
}
func EvalP2PKH(tx Transaction, inputIndex int, scriptSig []byte, scriptPubKey []byte) bool
Execution rules
- Reject immediately if
inputIndexis out of range. - Start with empty stack.
- Execute
scriptSig, thenscriptPubKey, on the same stack. - Opcode behavior:
OP_DUP: duplicate top stack item.OP_HASH256: replace top withHASH256(top).OP_EQUALVERIFY: pop two items; fail if not equal.OP_CHECKSIG: poppubkeythensignature; verify ECDSA overS; push1if valid else0.
- Script succeeds iff stack is non-empty and top item is not all-zero bytes.
Crypto details
- Curve:
elliptic.P256(). - Pubkey format: uncompressed
0x04 || X(32) || Y(32). - Treat pubkeys not exactly 65 bytes, wrong prefix, or not on curve as failed CHECKSIG verification (push 0).
- Signature format: ASN.1 DER (
ecdsa.VerifyASN1).
Scope and independent reconstruction
This is a generic interpreter for the stated subset; it does not enforce a P2PKH template or a clean stack. CHECKSIG failures, including invalid public keys, push 0 and continue. EQUALVERIFY mismatch fails immediately. A later push can therefore make a program with a failed CHECKSIG end truthy.
The digest trusts the supplied tx.ID. Changing tx.Outputs while keeping tx.ID, inputIndex and scriptPubKey unchanged does not change the digest. Deriving and checking an ID from transaction bytes is a surrounding system's responsibility.
Before coding, trace 02 76 aa 76: the first instruction pushes literal bytes
76aa, then DUP executes at offset 3. Explain why the aa byte is not HASH256.
Use the lesson for the full P2PKH-lite trace, then reconstruct it independently.
Constraints
0 <= len(scriptSig), len(scriptPubKey) <= 100000 <= len(tx.Inputs) <= 100000
Common mistakes
- Big-endian
inputIndexin sighash. - Wrong pop order in
OP_CHECKSIG. - Forgetting to reject malformed pushdata.
- Accepting invalid pubkey point not on curve.
- Treating any non-empty top as true (must reject all-zero top).