Pay-to-Contract Scripts - Advanced Pattern
15. Pay-to-Contract Scripts — Advanced Pattern
Overview
Pay-to-Contract (P2C) is a cryptographic technique — not a distinct on-chain script type — where the recipient's public key is tweaked to commit to a contract or message, creating a provably linked commitment. The sender tweaks the recipient's key with a hash of the contract terms; the recipient can always derive the corresponding private key and prove the payment was intended for that specific contract.
Mathematical Foundation
Standard key pair: (d, P) where P = d·G
P2C key tweaking:
t = hash(P || contract_data) <- tweak scalar
P' = P + t·G <- tweaked public key (payment address)
d' = d + t <- corresponding private key (known only to recipient)
Proof of commitment:
Given (P, P', contract_data):
Verify: P' - P == hash(P || contract_data)·G
This proves P' commits to contract_data relative to key P
On-Chain Implementation
The payment is made to a standard script (P2WPKH, P2TR) using the tweaked key P':
def pay_to_contract(recipient_pubkey: bytes, contract_data: bytes) -> bytes:
"""
Returns a P2WPKH locking script committing to contract_data.
"""
# Compute tweak
tweak_input = recipient_pubkey + contract_data
t = int.from_bytes(sha256(tweak_input), 'big') % SECP256K1_ORDER
# Tweaked public key
P = decode_pubkey(recipient_pubkey)
t_G = ec_multiply(t, GENERATOR)
P_prime = ec_add(P, t_G)
tweaked_pubkey = encode_pubkey(P_prime)
# Build P2WPKH script
keyhash = hash160(tweaked_pubkey)
return bytes([0x00, 0x14]) + keyhash
Use Cases
1. Invoice commitment: Payment proves agreement to specific invoice terms
2. Document notarization: Payment to tweaked key proves document existed at block time
3. Statechains: Key transfer without on-chain transaction
4. BOLT 11/12 (Lightning): Payment secrets and payment metadata embedding
5. Taproot base: Taproot's key tweaking mechanism IS pay-to-contract
Relationship to Taproot
Taproot's output key is literally a P2C commitment to the Tapscript tree:
Internal key: P
Script tree Merkle root: R
Taproot tweak: t = tagged_hash("TapTweak", bytes(P) || R)
Output key: Q = P + t·G
The output key Q commits to both:
- The internal key P (for key path spending)
- All scripts in the Merkle tree (for script path spending)
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: