Tutorial 1: P2PKH Contract Introduction
P2PKH (Pay to Public Key Hash) is the most classic and common UTXO locking pattern. This tutorial doesn't start with a "tautological Hello World," but instead begins directly with a real, usable signature contract.
After completing this tutorial, you will be able to:
- Understand the locking conditions and unlocking parameters of
P2PKH - Write and compile a complete
p2pkhcontract - Grasp the necessity of
Clonein signature verification - Verify both passing and failing execution paths using the debugger
What Problem Does P2PKH Solve
The goal is simple: only the person who owns a certain private key can spend this UTXO.
The on-chain verification process typically has two steps:
- Public key hash match:
Hash160(pubKey) == pubKeyHash - Signature valid:
CheckSig(sig, pubKey) == true
Where pubKeyHash is the value fixed into the contract at deployment time (typically the hash of the recipient's address).
Step 1: Write the p2pkh Contract
Create file p2pkh.ct:
Contract P2PKH:
def verify(sig: hex, pubKey: hex):
pubKey_copy = pubKey.Clone()
pubKeyHash = Hash160(pubKey_copy)
EqualVerify(pubKeyHash, self.pubKeyHash)
result = CheckSig(sig, pubKey)This code corresponds to the standard P2PKH logic:
verify(sig, pubKey): Submit signature and public key when unlockingself.pubKeyHash: The target public key hash stored in the contractEqualVerify(...): Immediately terminate if hashes don't matchCheckSig(...): Verify whether the signature and public key match
Step 2: Why Clone Is Necessary
The key line is:
pubKeyCopy = pubKey.Clone()Reason: Hash160(pubKeyCopy) consumes its input value, and CheckSig(sig, pubKey) later still needs the original pubKey. If you don't Clone first, the compiler will report "variable has been consumed."
Rules of thumb:
- When the same variable needs to be used by multiple operations,
Clonebefore the first consumption - The last use can directly consume the original variable
Step 3: Compile the Contract
./utxo_compiler p2pkh.ctAfter successful compilation, the JSON output should include:
- Contract name
P2PKH - Public function
verify(sig: hex, pubKey: hex) - Corresponding bytecode/debug info (depending on compilation parameters)
Step 4: Enter the Debugger to Verify
./utxo_compiler p2pkh.ct --debugAfter running, input parameters (example):
Enter parameters for verify:
sig [hex]: 0x3045022100...
pubKey [hex]: 0x03ab12...Passing Path
Use a public key matching pubKeyHash and provide the corresponding signature:
EqualVerifypassesCheckSigreturns 1- Contract ultimately returns true
Failing Path
You can intentionally input incorrect parameters to test rejection logic:
- Public key doesn't match: terminates directly at
EqualVerify - Signature doesn't match:
CheckSigreturns 0, ultimately fails
FAQ
1) What format should pubKeyHash use?
Use hex, typically 20 bytes (the result of Hash160).
2) Why use EqualVerify instead of Equal?
EqualVerify terminates immediately on failure, better matching the "must satisfy" constraint semantics, and the code is more concise.
3) What is the relationship between P2PKH and addresses?
Wallet addresses are typically an encoded representation of pubKeyHash. What is actually compared in the contract is the hash value itself, not the address string.
Summary
You have completed the first real, usable UTXO contract, and have mastered the core of P2PKH:
- Unlocking function
verifyverifies identity (hash + signature) Cloneresolves ownership/consumption issues- The debugger can verify both the success and failure paths
Next Steps
- Tutorial 2: Counter Contract — Learn how to design contracts with state-advancing logic