BitcoinMachine
TECHNICAL_DOC // KEYS / KEY-TWEAKING
KEY
TWEAKING
Key tweaking is the process of adding a scalar to a private key (or its corresponding point to a public key) to derive a modified key that commits to extra data. Taproot uses key tweaking to embed a Merkle root of spending scripts into the on-chain public key — so that the key-path and script-path spends are cryptographically bound into a single address with no on-chain overhead.
INTERNAL KEY → OUTPUT KEY DERIVATION
Given: internal_privkey = p (32 bytes) internal_pubkey = P = p×G (x-only, 32 bytes) Script tree (optional): scripts = {leaf_A, leaf_B, leaf_C} merkle_root = TapBranch(TapLeaf(leaf_A), ...) Tweak computation: t = H_taptweak(P || merkle_root) ← tagged hash (if no scripts: t = H_taptweak(P)) Output public key (on-chain): Q = P + t×G ← tweaked pubkey (x-only in scriptPubKey) Output private key (for key-path signing): q = p + t (mod n) scriptPubKey: OP_1 <32-byte Q> ← P2TR address
SPENDING PATHS
KEY-PATH vs SCRIPT-PATH SPEND
Key-path spend (most private, cheapest): Signer knows q = p + t → produces Schnorr sig on Q Witness: <sig> (64 or 65 bytes only) On-chain: indistinguishable from single-key P2TR Script-path spend (reveals branch only): Signer provides: script, control block Control block: version || P || merkle_siblings Verifier recomputes Q from P + scripts → checks match Only the executed leaf's script is revealed Other script branches remain private Observer cannot tell from address alone: - Whether scripts exist - How many scripts exist - Which script will be used
NUMS — Key-Path Disabled
SCRIPT-ONLY SPENDS
When no key-path spend should be possible, a Nothing-Up-My-Sleeve (NUMS) point is used as the internal key. It has no known discrete log, making key-path spending provably impossible.
NUMS point for Taproot (BIP 341): H = lift_x(SHA256("TapTweak")) This is: 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547... Usage in descriptor: tr(H, {pk(key_A), pk(key_B)}) ↑ H is unspendable via key-path → scripts MUST be used Why it works: H is a valid curve point (from a hash) No one knows the scalar h such that h×G = H Therefore: q = h + t is unknown → key-path spend impossible Verifiable: anyone can confirm H is derived from a hash, not from a known private key.
NUMS is used in Lightning channel outputs, vaults, and any construct where script-only spending must be enforced on-chain.
Tagged Hashes — Domain Separation
BIP 340/341
BIP 340 introduces tagged hashes to prevent cross-protocol hash collisions. Every Taproot hash operation uses a domain-specific tag prefix.
Tagged hash construction: H_tag(x) = SHA256(SHA256(tag) || SHA256(tag) || x) Tags used in Taproot: "TapLeaf" → hashes script leaves "TapBranch" → hashes pairs of branches "TapTweak" → hashes the final key tweak "BIP0340/challenge" → Schnorr signature challenge Why two SHA256(tag) prefix: - First 64 bytes cached (same tag → same prefix) - Different tags → different hash domains - Prevents a hash output in one context being used as valid input in another context
TERMINOLOGY_INDEX
Key Tweak
A scalar t added to a private key (or t×G added to a public key) to commit extra data into the key.
Internal Key
The original key P before tweaking. Not published on-chain — only the output key Q appears in the scriptPubKey.
Output Key
Q = P + t×G. The tweaked key that appears on-chain in a P2TR scriptPubKey.
Control Block
Data provided in a script-path spend: version-byte-base58check/">version byte, internal pubkey, and Merkle proof for the executed leaf.
NUMS Point
A curve point with no known discrete log, used as internal key to disable key-path spending provably.
Tagged Hash
SHA256(SHA256(tag) || SHA256(tag) || data). Used throughout BIP 340/341 for domain separation.
TAPROOT / KEY TWEAKING
Taproot Key Tweaking
Taproot (BIP340/341/342) derives the output key Q from an internal key P by adding a scalar tweak: Q = P + t·G where t = HTapTweak(P_x || script_tree_hash). This binds a script spending path into the key itself — spending via key-path reveals nothing about scripts; spending via script-path reveals only the used branch.
BIP341 OUTPUT KEY DERIVATION
t = HTapTweak(Px || h)
Q = P + t·G

where Px = x-coordinate of internal key (32 bytes)
h = Merkle root of script tree (32 bytes, or empty for key-only)
HTapTweak(m) = SHA256(SHA256("TapTweak") || SHA256("TapTweak") || m)
Output address uses Qx (x-coordinate only, even-y convention)
Why tweak at all? Without a script tree the address could use Q = P directly — but BIP341 mandates a tweak of H(P_x) even when there are no scripts. This prevents key recovery attacks where a malicious party claims your output key was their unencumbered key all along. The tweak proves intent.
INTERNAL KEY → TAPROOT OUTPUT KEYBIP341 compliant
PATHWITNESSWHAT IS REVEALEDPRIVACY
key-path 64-byte Schnorr sig Nothing — looks identical to any Taproot spend Maximum
script-path script + control block (internal key + Merkle proof) The executed script leaf + Merkle path (not other branches) Partial
The control block in a script-path spend contains: the leaf version (0xc0), the internal key x-coordinate, and sibling hashes forming a Merkle path. A verifier recomputes the Merkle root and checks the output key matches.
COMPUTE TAPLEAF + TAPBRANCH HASHES
A TapLeaf is HTapLeaf(version || compact_size(script) || script). Two sibling leaves form a TapBranch: HTapBranch(min(A,B) || max(A,B)).