Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,36 @@ export default {
{ text: "ASM Representation", link: "/script/asm.md" },
{ text: "Push Operators", link: "/script/push.md" },
{ text: "Numbers in Script", link: "/script/numbers.md" },
{ text: "Standard Payment Scripts", collapsed: false, items: [
{
text: "P2PK",
link: "/script/standard-payment-scripts/P2PK.md"
},
{
text: "P2PKH",
link: "/script/standard-payment-scripts/P2PKH.md"
},
{
text: "P2SH",
link: "/script/standard-payment-scripts/P2SH.md"
},
{
text: "P2SH-P2WPKH",
link: "/script/standard-payment-scripts/P2SH-P2WPKH.md"
},
{
text: "P2WPKH",
link: "/script/standard-payment-scripts/P2WPKH.md"
},
{
text: "P2WSH",
link: "/script/standard-payment-scripts/P2WSH.md"
},
{
text: "P2TR",
link: "/script/standard-payment-scripts/P2TR.md"
}
] }
]
},

Expand Down
2 changes: 1 addition & 1 deletion script/index.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Bitcoin Script
The pages in this section explore the bitcoin scripting system.
The pages in this section explore the bitcoin scripting system.
62 changes: 62 additions & 0 deletions script/standard-payment-scripts/P2PK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# P2PK

:::info
**Script Type:** Pay-to-PubKey (P2PK)
**Byte representation:** `0x21` + pubkey + `0xac` (compressed) or `0x41` + pubkey + `0xac` (uncompressed)
**Address format:** None — P2PK has no standard Bitcoin address encoding
**Short description:** A script that locks funds directly to a public key.
:::

Pay-to-PubKey (P2PK) is one of the simplest and oldest Bitcoin payment scripts. It locks funds directly to a public key, requiring a valid signature from the corresponding private key to spend them.

### Historical Context

P2PK is the original Bitcoin payment script used by Satoshi Nakamoto in the genesis block and in early coinbase outputs. In those early days, uncompressed 65-byte public keys were the norm; compressed 33-byte keys became standard later. P2PK has since been superseded by P2PKH, which improves privacy by withholding the public key from the locking script. Notably, P2PK has no standard address format — funds locked in P2PK outputs cannot be represented as a traditional Bitcoin address.

### Operation

**Locking (ScriptPubKey):**
1. The recipient's public key is embedded directly in the script.
2. `OP_CHECKSIG` signals that a valid signature will be required to spend.

**Unlocking (ScriptSig):**
1. The spender pushes a DER-encoded signature (appended with a sighash type byte) onto the stack.
2. `OP_CHECKSIG` pops the signature and the public key, verifies the ECDSA signature against the transaction data, and pushes `1` (true) if valid or `0` (false) if not.
3. If the result is `0`, the script fails.

### Notes

- P2PK exposes the public key in the locking script itself, making it visible on-chain before any spending occurs. P2PKH only reveals the public key at spend time.
- There is no P2PK address format. You cannot represent a P2PK output as a standard Base58Check or bech32 address.
- Uncompressed public keys (65 bytes, prefix `04`) were common in early Bitcoin. Compressed keys (33 bytes, prefix `02` or `03`) are standard today.
- A large amount of early Bitcoin — including Satoshi's coinbase rewards — is locked in P2PK outputs. Their public keys are already visible on-chain, making them potentially vulnerable to future quantum computing attacks.

### Example

#### ScriptPubKey

```shell
# ASM (compressed pubkey)
<33-byte-pubkey> OP_CHECKSIG

# ASM (uncompressed pubkey, early Bitcoin)
<65-byte-pubkey> OP_CHECKSIG

# Raw bytes (compressed)
21 <33-byte-pubkey> ac

# Raw bytes (uncompressed)
41 <65-byte-pubkey> ac
```

#### ScriptSig

```shell
# ASM
<sig>

# Raw bytes
# Signature is DER-encoded + 1-byte sighash type (01 = SIGHASH_ALL)
<siglen> <DER-encoded-sig> 01
# Example: 48 <71-byte DER sig> 01
```
55 changes: 55 additions & 0 deletions script/standard-payment-scripts/P2PKH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# P2PKH

:::info
**Script Type:** Pay-to-PubKey-Hash (P2PKH)
**Byte representation:** `0x76 0xa9 0x14` + 20-byte hash + `0x88 0xac`
**Address format:** Base58Check encoded, starts with `1` (mainnet)
**Short description:** A script that locks funds to the HASH160 of a public key.
:::

Pay-to-PubKey-Hash (P2PKH) is the most widely used legacy Bitcoin payment script. It locks funds to the HASH160 (RIPEMD160 of SHA256) of a public key, requiring the spender to reveal the public key and provide a valid signature to unlock the funds.

### Historical Context

P2PKH was introduced early in Bitcoin's history as an improvement over P2PK. By hashing the public key, it keeps the key hidden until spend time — improving both privacy and efficiency. P2PKH addresses are Base58Check encoded and start with `1` on mainnet (e.g. `1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf Na`). This was the dominant address format until native SegWit addresses were introduced in 2017.

### Operation

1. The ScriptPubKey contains the HASH160 of the recipient's public key (always 20 bytes).
2. `OP_DUP` duplicates the top stack item (the public key provided in the ScriptSig).
3. `OP_HASH160` hashes the duplicate: first SHA256, then RIPEMD160, producing a 20-byte hash.
4. The 20-byte expected hash from the script is pushed onto the stack.
5. `OP_EQUALVERIFY` checks that the computed hash matches. If not, the script fails immediately.
6. `OP_CHECKSIG` verifies the signature against the original (non-hashed) public key. Pushes `1` if valid.

### Notes

- P2PKH improves privacy over P2PK: the public key is only revealed when funds are spent, not when they are received.
- The pubkey hash is always 20 bytes — the result of HASH160 (SHA256 then RIPEMD160). It is pushed in the raw script using opcode `0x14` (push 20 bytes).
- On mainnet, P2PKH addresses start with `1` due to the Base58Check version byte `0x00`.
- Once a P2PKH address has been spent from, the public key becomes visible on-chain. Reusing the same address after spending weakens privacy and exposes the public key to future scrutiny.

### Example

#### ScriptPubKey

```shell
# ASM
OP_DUP OP_HASH160 <20-byte-pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG

# Raw bytes
76 a9 14 <20-byte-pubkeyhash> 88 ac
# 76 = OP_DUP, a9 = OP_HASH160, 14 = push 20 bytes, 88 = OP_EQUALVERIFY, ac = OP_CHECKSIG
```

#### ScriptSig

```shell
# ASM
<sig> <pubkey>

# Raw bytes (compressed pubkey example)
<siglen> <DER-encoded-sig> 01 21 <33-byte-pubkey>
# siglen: typically 0x47 or 0x48 (71 or 72 bytes incl. sighash type)
# 21 = push 33 bytes (compressed pubkey, prefix 02 or 03)
```
72 changes: 72 additions & 0 deletions script/standard-payment-scripts/P2SH-P2WPKH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# P2SH-P2WPKH

:::info
**Script Type:** Pay-to-Script-Hash of Pay-to-Witness-PubKey-Hash (P2SH-P2WPKH)
**Byte representation:** `0xa9 0x14` + 20-byte hash + `0x87` (ScriptPubKey — identical to P2SH)
**Address format:** Base58Check encoded, starts with `3` (mainnet) — indistinguishable from a plain P2SH address
**Short description:** A wrapped SegWit script: a P2SH address that embeds a native P2WPKH witness program.
:::

P2SH-P2WPKH is a "wrapped SegWit" format introduced alongside SegWit to allow SegWit adoption on wallets and services that only supported legacy P2SH addresses. It wraps a P2WPKH witness program inside a standard P2SH locking script. The actual signature and public key travel in the **witness** field, not the ScriptSig.

### Historical Context

SegWit (BIP 141) was activated on mainnet in August 2017. To ease the transition, BIP 49 defined P2SH-P2WPKH as a compatibility bridge: it uses a `3...` address (familiar to wallets and exchanges) but takes advantage of the SegWit witness structure under the hood. This allowed services to send to SegWit-compatible wallets without needing to support the new bech32 address format. Today, native P2WPKH (bech32 `bc1q...`) is preferred for lower fees and cleaner semantics.

### Operation

P2SH-P2WPKH has three layers:

1. **ScriptPubKey** — a standard P2SH script containing the HASH160 of the redeem script.
2. **ScriptSig** — contains *only* the serialized redeem script, which is the P2WPKH witness program: `OP_0 <20-byte-pubkeyhash>`.
3. **Witness** — contains the signature and public key, exactly as in native P2WPKH.

**Spending flow:**
1. The script engine sees a standard P2SH ScriptPubKey: `OP_HASH160 <hash> OP_EQUAL`.
2. The ScriptSig provides only the redeem script (`OP_0 <pubkeyhash>`). The engine hashes it and verifies the match.
3. The redeem script is recognized as a version-0 witness program (22 bytes: `00 14 <20-byte-hash>`).
4. The witness field is evaluated as P2WPKH: `OP_DUP OP_HASH160 <pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG` is run against the witness `<sig>` and `<pubkey>`.
5. If all checks pass, the script succeeds.

### Notes

- The ScriptPubKey is structurally identical to a regular P2SH output — you cannot distinguish P2SH-P2WPKH from plain P2SH by the address alone.
- The ScriptSig contains **only** the redeem script. Signatures and public keys go in the witness field.
- Witness data benefits from the SegWit weight discount (1 weight unit per byte instead of 4), making transactions cheaper.
- BIP 49 defines the HD wallet derivation path for P2SH-P2WPKH: `m/49'/0'/0'/account'/change/index`.
- This format is considered a transitional compatibility layer. New wallets should prefer native P2WPKH with bech32 addresses.

### Example

#### ScriptPubKey

```shell
# ASM (standard P2SH — identical structure to any P2SH output)
OP_HASH160 <20-byte-hash-of-redeem-script> OP_EQUAL

# Raw bytes
a9 14 <20-byte-hash-of-redeem-script> 87
```

#### ScriptSig

```shell
# ASM (only the redeem script — the P2WPKH witness program)
OP_0 <20-byte-pubkeyhash>

# Raw bytes (the 22-byte redeem script pushed as a single data item)
16 00 14 <20-byte-pubkeyhash>
# 16 = push 22 bytes, 00 = OP_0, 14 = push 20 bytes
```

#### Witness

```shell
# Two items in the witness stack (not in the ScriptSig)
<sig>
<pubkey>

# Example (compressed pubkey)
<siglen> <DER-encoded-sig> 01 # sig + SIGHASH_ALL byte
21 <33-byte-compressed-pubkey> # 21 = push 33 bytes
```
57 changes: 57 additions & 0 deletions script/standard-payment-scripts/P2SH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# P2SH

:::info
**Script Type:** Pay-to-Script-Hash (P2SH)
**Byte representation:** `0xa9 0x14` + 20-byte hash + `0x87`
**Address format:** Base58Check encoded, starts with `3` (mainnet)
**Short description:** A script that locks funds to the HASH160 of a redeem script.
:::

Pay-to-Script-Hash (P2SH) allows funds to be locked to the HASH160 of an arbitrary redeem script. At spend time, the spender reveals the redeem script and provides all inputs needed to satisfy it.

### Historical Context

P2SH was introduced in BIP 16, activated in March 2012. Before P2SH, complex spending conditions (like multisig) required embedding the full script in the ScriptPubKey, which was costly and burdensome for the sender. P2SH shifts that complexity to the spender: the sender only needs to know a 20-byte hash, while the spender provides the full script. P2SH addresses are Base58Check encoded and start with `3` on mainnet (e.g. `3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy`). It is still widely used today for multisig wallets and as a wrapper for SegWit outputs (P2SH-P2WPKH, P2SH-P2WSH).

### Operation

1. The ScriptPubKey contains the HASH160 of the redeem script (always 20 bytes).
2. To spend, the ScriptSig must push:
- All arguments required to satisfy the redeem script (e.g. signatures for a multisig).
- The serialized redeem script itself as the **final** pushed item.
3. The script engine hashes the top stack item (the redeem script) and checks it matches the hash in the ScriptPubKey via `OP_EQUAL`.
4. If the hash matches, the redeem script is deserialized and executed with the remaining stack items as inputs.
5. If execution of the redeem script succeeds with a truthy top value, the script passes.

### Notes

- The serialized redeem script must always be the **last** item pushed in the ScriptSig.
- The script hash is always 20 bytes (HASH160 output), pushed in the raw ScriptPubKey using `0x14` (push 20 bytes).
- P2SH shifts the transaction size cost and script encoding complexity to the spender rather than the sender.
- On mainnet, P2SH addresses start with `3` due to the Base58Check version byte `0x05`.
- P2SH is also used as a SegWit compatibility wrapper: P2SH-P2WPKH and P2SH-P2WSH wrap native SegWit witness programs inside a P2SH address.

### Example

#### ScriptPubKey

```shell
# ASM
OP_HASH160 <20-byte-scripthash> OP_EQUAL

# Raw bytes
a9 14 <20-byte-scripthash> 87
# a9 = OP_HASH160, 14 = push 20 bytes, 87 = OP_EQUAL
```

#### ScriptSig

```shell
# ASM (example: 2-of-3 multisig redeem script)
OP_0 <sig1> <sig2> <redeem_script>

# Raw bytes
00 <siglen1> <sig1> <siglen2> <sig2> <scriptlen> <redeem_script>
# OP_0 is required due to a known off-by-one bug in OP_CHECKMULTISIG
# The redeem script is always the last pushed item
```
86 changes: 86 additions & 0 deletions script/standard-payment-scripts/P2TR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# P2TR

:::info
**Script Type:** Pay-to-Taproot (P2TR)
**Byte representation:** `0x51 0x20` + 32-byte x-only tweaked pubkey (always 34 bytes total)
**Address format:** bech32m encoded, starts with `bc1p` (mainnet)
**Short description:** A SegWit v1 script that locks funds to a Taproot tweaked x-only public key, supporting both key path and script path spending.
:::

Pay-to-Taproot (P2TR) is the most expressive Bitcoin payment type available today. It locks funds to a 32-byte x-only public key that commits to both a key path (a single Schnorr signature) and an optional Merkle tree of spending scripts. Unexercised script branches are never revealed on-chain.

### Historical Context

P2TR was introduced by the Taproot soft fork, which bundled three BIPs: BIP 340 (Schnorr Signatures), BIP 341 (Taproot), and BIP 342 (Tapscript). It activated on mainnet in November 2021 at block 709632. Taproot replaced ECDSA with Schnorr signatures, enabling key and signature aggregation schemes like MuSig2. P2TR addresses use bech32m encoding (BIP 350) and start with `bc1p` on mainnet. BIP 86 defines the HD wallet derivation path: `m/86'/0'/0'`.

### Operation

P2TR supports two spending paths:

**Key path spend:**
1. The ScriptPubKey contains `OP_1 <32-byte-tweaked-x-only-pubkey>`.
2. The ScriptSig is **empty**.
3. The spender provides a single 64-byte Schnorr signature in the witness field.
4. The signature is verified against the tweaked public key using BIP 340 Schnorr verification. If valid, the script succeeds.

**Script path spend:**
1. Same ScriptPubKey — only the witness data differs.
2. The spender provides in the witness: all inputs required by the leaf script, the leaf script itself, and a control block.
3. The control block encodes the internal public key and a Merkle proof demonstrating that the leaf script is committed to by the output key.
4. The script engine verifies the Merkle proof, then executes the leaf script with the provided inputs.
5. If the script executes successfully, the spend is valid.

### Notes

- The output key is a *tweaked* key: `Q = P + t·G`, where `t = HASH(P || merkle_root)`. This mathematically commits all script paths into the key itself.
- If no script paths are needed, the key is tweaked with just the internal key hash, resulting in a key-path-only output that is indistinguishable on-chain from one with a full script tree.
- All unexercised script branches remain private — only the executed path (or none, for key path) is ever revealed.
- Schnorr signatures (BIP 340) are 64 bytes, more compact than DER-encoded ECDSA, and enable key aggregation.
- The ScriptSig is always **empty** for P2TR.
- All P2TR outputs look identical on-chain regardless of the underlying script tree, improving privacy for all users.
- BIP 86 derivation path: `m/86'/0'/0'/account'/change/index`.

### Example

#### ScriptPubKey

```shell
# ASM
OP_1 <32-byte-x-only-tweaked-pubkey>

# Raw bytes (always 34 bytes)
51 20 <32-byte-x-only-tweaked-pubkey>
# 51 = OP_1 (witness version 1), 20 = push 32 bytes
```

#### ScriptSig

```shell
# Always empty for P2TR
(empty)
```

#### Witness — Key Path Spend

```shell
# Single Schnorr signature (64 bytes)
<64-byte-schnorr-sig>

# With non-default sighash type appended (optional)
<64-byte-schnorr-sig> <sighash_type>
# SIGHASH_DEFAULT (implicit) = commits to all inputs and outputs
```

#### Witness — Script Path Spend

```shell
# Stack items required by the script, then the script, then the control block
<script_input_1> ... <script_input_n>
<leaf_script>
<control_block>

# Control block structure:
# [leaf_version + output_key_parity (1 byte)]
# [internal_x_only_pubkey (32 bytes)]
# [merkle_proof_node_1 (32 bytes)] ... [merkle_proof_node_n (32 bytes)]
```
Loading