Glossary / Developer reference
OP_CHECKSIG
- Definition
- OP_CHECKSIG takes a public key and a signature off the stack and verifies that the signature commits to this transaction, which is the check that actually proves you own the coins.
Nearly every bitcoin ever spent has passed through this one opcode, byte 0xac. It builds a digest of the transaction according to the sighash byte glued to the end of the signature, checks the signature against the public key, and pushes true or false. If it pushes false the spend does not happen, which is the whole of what owning bitcoin means in code.
How it works
OP_CHECKSIG pops two items, the public key first and the signature underneath it, then does three separate jobs that people usually collapse into one.
First it decides what was signed. The final byte of a signature is not part of the signature: it is a flag saying which parts of the transaction the digest covered. The overwhelmingly common value is 0x01, meaning all inputs and all outputs, so altering any part of the transaction after signing invalidates it. The recipe for building that digest has been replaced twice, by BIP-143 for SegWit version 0 spends and again by BIP-341 for Taproot, both times to stop the hashing cost growing with the square of the transaction size.
Second it checks the encoding, and bitcoin is unusually strict here. A legacy signature must be valid DER, at most 72 bytes including the flag byte, with the S value in the lower half of the curve order. The strict encoding rule arrived as BIP-66 in July 2015, and the switchover orphaned six blocks in a single afternoon because several large pools were building on block headers they had not validated. It remains the clearest demonstration that mining and validating are different jobs.
Third it verifies. Legacy and SegWit version 0 spends use ECDSA over secp256k1. Tapscript uses a 64-byte Schnorr signature under BIP-340 instead, requires a failed check to push an empty item rather than anything else, and charges the script 50 weight units of budget for each signature checked.
Where you see it
OP_CHECKSIG closes almost every script that exists. A legacy output ends with it, a native single-key SegWit spend runs the same template with the same final opcode even though those bytes never appear on the chain, and Lightning's commitment scripts end with it after their timelock and revocation branches.
The exception is worth knowing when you read a block explorer. A Taproot key-path spend runs no script at all: the node checks a single signature directly against the 32-byte key in the output. There is no assembly to display and no opcode to point at, which is exactly why that spend type is the cheapest and the most private available, and why a bc1p address that is only ever spent by its owner looks identical on chain to every other one.
OP_CHECKSIG vs OP_CHECKSIGADD
OP_CHECKSIGADD exists because OP_CHECKSIG cannot be counted. In a multi-party script you need to know how many of the signatures presented were good, and the old design answered that with a single opcode that swallowed a whole list of keys and signatures at once. Tapscript replaced it with an opcode that verifies one signature and adds one to a running total on the stack, so a 2-of-3 policy becomes a short sequence ending in a comparison against 2. The change is not cosmetic: counting one signature at a time is what allows Schnorr signatures to be verified in batches, and batch verification is where Taproot's validation speed comes from.