TeachMeBitcoin

Fully Annotated P2TR Key Path Execution

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

6. Fully Annotated P2TR Key Path Execution

Pay-to-Taproot (P2TR) key path spending is the simplest and most private way to spend a Taproot output. It uses a single Schnorr signature against the tweaked public key embedded in the output.

Creating the P2TR Output

def create_p2tr_output(internal_pubkey, script_tree=None):
    """
    Create a P2TR output committing to an internal key
    and optionally a script tree.

    internal_pubkey: 32-byte x-only public key
    script_tree: optional MAST of spending scripts
    """
    if script_tree is None:
        # No scripts: tweak with empty tree
        merkle_root = b''
        tweak_data = internal_pubkey  # Just the key
    else:
        merkle_root = compute_merkle_root(script_tree)
        tweak_data = internal_pubkey + merkle_root

    # Compute the tweak
    tweak = tagged_hash("TapTweak", tweak_data)
    tweak_int = int.from_bytes(tweak, 'big')

    # Compute tweaked public key
    # P' = P + tweak*G
    internal_point = lift_x(int.from_bytes(internal_pubkey, 'big'))
    tweak_point = point_mul(G, tweak_int)
    tweaked_point = point_add(internal_point, tweak_point)

    # Get x-only representation
    tweaked_pubkey_x = tweaked_point.x.to_bytes(32, 'big')

    # scriptPubKey: OP_1 <32-byte-tweaked-pubkey>
    return bytes([0x51, 0x20]) + tweaked_pubkey_x

Key Path Spending Transaction

Input being spent:
  scriptPubKey: 5120 <32-byte-tweaked-pubkey>
  (OP_1 = 0x51, push 32 bytes = 0x20, then the key)

Spending transaction input:
  scriptSig: (empty — key path uses witness only)
  witness: [<64-or-65-byte-schnorr-signature>]

Schnorr Signature Format

Key path Schnorr signature formats:

64 bytes (SIGHASH_DEFAULT = 0x00):
<32-byte R_x> <32-byte s>
No sighash type byte appended — SIGHASH_DEFAULT is implied

65 bytes (any other sighash type):
<32-byte R_x> <32-byte s> <1-byte sighash_type>
sighash_type can be: 0x01 (ALL), 0x02 (NONE), 0x03 (SINGLE)
or with ANYONECANPAY: 0x81, 0x82, 0x83

Signing and Verification

def sign_taproot_key_path(private_key, tx, input_index, utxos, sighash_type=0x00):
    """
    Create a Schnorr signature for a Taproot key path spend.
    Implements BIP 340 signing with BIP 341 sighash.
    """
    # Compute the tweaked private key
    # d = private_key + tweak (mod curve order)
    tweak = compute_tap_tweak(private_key_to_pubkey(private_key))
    tweaked_private_key = (private_key + int.from_bytes(tweak, 'big')) % SECP256K1_ORDER

    # Compute sighash using BIP 341 algorithm
    sighash = compute_taproot_sighash(tx, input_index, utxos, sighash_type)

    # BIP 340 Schnorr signing:
    # 1. Generate deterministic nonce k
    rand = os.urandom(32)  # Optional auxiliary randomness
    t = xor(tweaked_private_key_bytes, tagged_hash("BIP0340/aux", rand))
    k = int.from_bytes(tagged_hash("BIP0340/nonce", t + pubkey + sighash), 'big') % ORDER

    # 2. R = k*G
    R = point_mul(G, k)

    # 3. If R.y is odd, negate k
    if R.y % 2 != 0:
        k = ORDER - k

    # 4. e = H("BIP0340/challenge", R.x || P.x || msg)
    e = int.from_bytes(
        tagged_hash("BIP0340/challenge", 
                    R.x.to_bytes(32,'big') + 
                    tweaked_pubkey_x + 
                    sighash), 'big'
    ) % ORDER

    # 5. s = k + e*d (mod n)
    s = (k + e * tweaked_private_key) % ORDER

    # 6. Signature = (R.x, s)
    sig = R.x.to_bytes(32, 'big') + s.to_bytes(32, 'big')

    if sighash_type != 0x00:
        sig += bytes([sighash_type])

    return sig

Technical Insight

This topic covers essential mechanics for Chapter 12. Understanding these details is key to mastering advanced Bitcoin script constructions like Taproot and specialized covenants.

☕ Help support TeachMeBitcoin

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:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!