Taproot

Technical explanation

Diagram showing a summary of how taproot works in Bitcoin

Taproot was a major upgrade to Bitcoin activated in 2021.

The primary upgrade was the introduction of the Pay To Taproot (P2TR) locking script, which allows you to unlock an output using a choice of custom scripts.

There were also a couple of extra changes:

  1. Schnorr Signatures — The new P2TR locking script uses the more efficient Schnorr signature scheme instead of ECDSA.
  2. Tapscript — The custom locking scripts inside P2TR use a slightly modified version of Script called Tapscript.

So the taproot upgrade just introduced a new and useful locking mechanism for outputs, which use Schnorr Signatures and Tapscript. This was a fairly large technical upgrade overall, which makes it the biggest major upgrade to Bitcoin since Segregated Witness (2017).

This page focuses on how to construct and spend the new P2TR locking script.

Basics

How does Taproot work?

Diagram showing the basics of a key path spend and a script path spend for a P2TR locking script

A P2TR locking script can be unlocked in one of two ways:

  1. Key Path Spend - This is where you provide a signature for the public key used to create the locking script. This works in a similar way to a simple P2WPKH.
  2. Script Path Spend - This is where you use one of multiple possible custom scripts to unlock the output. This is like using P2WSH, but you have multiple custom scripts to choose from.

The interesting part of P2TR is that all of the custom scripts are organized in to a tree. This script tree is used to create a merkle root, which is a fingerprint for all the different ways the output can be unlocked. This fingerprint (tweak) is then combined with the public key to create a tweaked public key.

This tweaked public key is what gets placed inside the locking script.

You can then unlock this locking script in one of two ways:

  1. Via the public key by tweaking its private key to create a signature for the tweaked public key. This is a key path spend.
  2. Using one of the custom scripts in the tree by providing a merkle path to prove that your chosen script was used to create the merkle root that was used to create the tweaked public key. This is a script path spend.

In short, both of these spending options are embedded in to a single tweaked public key (called the taproot).

The beauty of the P2TR locking mechanism is that the tweaked public key can be unlocked with a single signature. This signature is created by first tweaking the original private key by the same value as the tweak used on the original public key. This works thanks to the mathematics of elliptic curves.

The official name for the tweaked public key is the taproot. But to make the upcoming explanations clearer I usually just refer to it as the tweaked public key from here on out.

Benefits

What are the benefits of Taproot?

The design of the Pay To Taproot (P2TR) locking script provides a few benefits.

In short, it provides the flexibility to place a variety of different locking conditions on an output in a way that is both private and efficient.

1. Flexibility

The P2TR locking scripts allows you to place multiple different spending conditions (custom scripts) on a single output.

You can think of the key path spend as the "default" method, and the script path spend as alternatives in the event the default method is not an option:

It is almost always the case that interesting scripts have a logical top level branch which allows satisfaction of the contract with nothing other than a signature by all parties. Other branches would only be used where some participant is failing to cooperate.
Gregory Maxwell, [bitcoin-dev] Mailing List (2018)

2. Privacy

Thanks to the merkle tree structure used for the custom scripts, only a single script is actually revealed when you unlock a P2TR using the script path spend.

So you could construct a script tree with hundreds of different custom locking scripts, but only the custom script you use will be revealed when you unlock it (the rest will only appear as hashes of the original scripts).

This means all the other possible spending conditions of a P2TR are kept private.

If you spend a P2TR using the key path spend, none of the custom scripts in the tree are revealed. So anyone looking at a P2TR spent in this way wouldn't know if there were any additional spending conditions.

3. Efficiency

You'd think that including multiple different spending conditions on an output would create a large and expensive locking script.

However, thanks again to the merkle tree structure used for all the different custom scripts, all of the spending conditions are reduced down to a single 32-byte fingerprint (merkle root). You then only need to reveal a small portion of this tree (merkle path) when you come to unlock the output using your chosen script.

Furthermore, thanks to the mathematics of elliptic curve cryptography (i.e. point addition), we can merge this merkle root with a public key to create a tweaked public key. This tweaked public key allows the output to be spent using either a signature, or by using one of custom scripts that made up the merkle root.

As a result, all of the possible spending conditions can be embedded within a single 32-byte "tweaked public key" (aka the taproot), which is more efficient than placing multiple raw custom scripts directly inside the locking script.

A P2TR locking script is only 34 bytes in length, which is exactly the same size as a P2WSH (which contains one possible spending condition only).

If you use a large number of custom scripts in your script tree, the amount of data required for unlocking a P2TR will be larger due to the need to provide a longer merkle path to prove that your script was in the tree. But this tree structure means the amount of data you need to provide is relatively small compared to the size of the tree.

4. Upgradability

The P2TR locking mechanism in Bitcoin has been designed to allow for easy upgrades in the future.

This is why you'll see a few seemingly redundant fields (e.g. leaf version, annex, extension flag, sighash epoch) included as part of spending a P2TR. These additional fields may make the spending of a P2TR seem more complicated at first, but they're all there for a reason.

Construction

How do you create a P2TR?

Diagram showing the construction of a P2TR ScriptPubKey

Constructing a P2TR output is fairly straightforward. You just need to create a ScriptPubKey with the following pattern:

OP_1 OP_PUSHBYTES_32 <32-byte tweaked public key>

The tweaked public key is created by combining a public key with the merkle root from a tree of custom scripts.

The most complex part is organizing all the custom scripts in to a tree structure and calculating the merkle root from it. However, this script tree can be as simple or as complex as you want to make it (or you could just not use a script tree at all).

Below is an overview of the steps involved in creating a P2TR locking script.

See the examples for a technical step-by-step guide with actual data.

1. Public Key

aka Internal Public Key, Taproot Internal Key

The first step is to generate a public key.

This public key is used for the key path spend, which will be the "default" method for unlocking the P2TR output. It's generated in the same way as you'd generate any private key/public key pair used in Bitcoin.

However, P2TR uses 32-byte public keys as opposed to 33-byte compressed public keys. This is because P2TR locking scripts use Schnorr signatures, and public keys in this signature scheme contain the 32-byte x-coordinate only.

Example

public key = a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa4
Public Key

You can convert a standard 33-byte compressed public key to a 32-byte public key for P2TR by simply removing the first byte.

A public key is required to construct a P2TR locking script.

2. Script Tree

The script tree contains all the custom scripts you'd like to be able to use for unlocking the P2TR output (in addition to the public key spend option above).

You can use as many custom scripts as you want*, and you can organize the tree in any way you like.

*You're actually limited to 2128 different scripts. This is due to the maximum size of the merkle path in the control block, but this is a huge number.

The script tree and its merkle root is constructed in 3 steps:

A script tree is not required to construct a P2TR locking script.

1. Leaves

The leaves of the tree are made from the custom scripts you'd like to be able to use to unlock the P2TR output.

Each leaf contains two pieces of data:

  1. Version — This indicates the version of the upcoming custom script. This is used to allow for changes to the script language in future upgrades.
    • 192 (0xc0) = tapscript (current default)
  2. Script — This is your custom locking script (i.e. tapscript). It can contain any kind of script you like (the same as you might use in a P2WSH for example).

Example

leaf version = c0
leaf script  = 5187
Script
Why is the leaf version 192 (0xc0)?

The leaf version is encoded within the first byte of the control block, so it's ideal if this byte does not conflict with the first byte of other elements in the witness field for a script path spend.

There are a few restrictions on the choice of possible leaf versions:

This leaves the following bytes for use as distinctive leaf versions:

And seeing as 0xc0 is at the start of a range of non-conflicting bytes that we can choose from, the leaf version has been chosen to start at 0xc0 (192).

So the leaf version could have started at 1, which would be logical, but starting at 192 helps to prevent the control block from being confused with other elements in the witness for P2TR spends (whilst also allowing for successive leaf version numbers).

2. Organization

Diagram showing a balanced script tree where each script is equally likely to be executed Diagram showing an unbalanced script tree where some scripts are more likely to be executed than others

The next step is to organize your leaves in to a tree.

If you have a small number of leaves (1 or 2), the tree structure will be straightforward. However, if you have lots of leaves (3+) you have more flexibility in how you organize the leaves of the tree.

The only consideration in the organization of your leaves is that you want the script that is most likely to be executed closest to the root of the tree. This is because you will save on fees when you come to unlock this script later on, as the merkle path you need to provide will be shorter.

When constructing a [script tree], if the user believes that some of the branches are more likely to be executed, they may put them closer to the Script Root. It will save some witness space when the preferred branches are actually executed.
BIP 114: Merkelized Abstract Syntax Tree
The remaining scripts should be organized into the leaves of a binary tree. This can be a balanced tree if each of the conditions these scripts correspond to are equally likely. If probabilities for each condition are known, consider constructing the tree as a Huffman tree.
BIP 341: Taproot

Don't worry too much about the organization of your leaves if you're unsure. It just gives you the opportunity to save a small amount in fees if you know that some leaf scripts are more likely to be executed than others.

3. Merkle Root

The final step is to hash all of the leaves in the script tree to produce a single merkle root.

This merkle root is calculated in 3 steps:

1. Leaf Hash

Start by creating a tagged hash for each of the leaves.

The leaf version and leaf script must be in bytes. So convert the leaf script from ASM to hexadecimal bytes first.

Also, don't forget to include the size(leaf script), which is a compact size field indicating the size of the leaf script (in bytes).

Example

leaf version      = c0
size(leaf script) = 02
leaf script       = 5187

data = leaf version + size(leaf script) + leaf script
data = c0025187

leaf hash = tagged_hash("TapLeaf", data)
leaf hash = 6b13becdaf0eee497e2f304adcfa1c0c9e84561c9989b7f2b5fc39f5f90a60f6
Script
Compact Size
Tagged Hash

The size(script) + script format is referred to as a "serialized script". This format (i.e. prepending the script with a compact size field) is also used when hashing scriptpubkeys within the signature algorithm. This is easy to forget and is likely to trip you up the first time around.

2. Branch Hash

After calculating each leaf hash, you then calculate the subsequent branches for your tree.

Each branch hash is calculating by creating a tagged hash of previous leaf/branch hash pairs:

Depending on where you are at in the tree, the upcoming branch will be calculated using previous leaf hashes or branch hashes (or a combination of both).

It's important to note that the lower hash goes first. This is so the merkle root can be recalculated later on without having to provide information about the order of the hashes.

Example

┌────────┐ ┌────────┐                
│ leaf 1 │ │ leaf 2 │                
└───┬────┘ └───┬────┘                
    └────┬─────┘                     
     ┌───┴────┐ ┌────────┐         
     │branch 1│ │ leaf 3 │         
     └───┬────┘ └─────┬──┘           
         └─────┬──────┘              
           ┌───┴────┐ ┌────────┐     
           │branch 2│ │ leaf 4 │     
           └───┬────┘ └────┬───┘     
               └────┬──────┘         
                ┌───┴────┐ ┌────────┐
                │branch 3│ │ leaf 5 │
                └───┬────┘ └────┬───┘
                    └─────┬─────┘    
                      ┌───┴────┐     
                      │branch 4│     
                      └────────┘

leaf 1 version      = c0
size(leaf 1 script) = 02
leaf 1 script       = 5187
leaf 1 hash         = 6b13becdaf0eee497e2f304adcfa1c0c9e84561c9989b7f2b5fc39f5f90a60f6

leaf 2 version      = c0
size(leaf 2 script) = 02
leaf 2 script       = 5287
leaf 2 hash         = ed5af8352e2a54cce8d3ea326beb7907efa850bdfe3711cef9060c7bb5bcf59e

leaf 3 version      = c0
size(leaf 3 script) = 02
leaf 3 script       = 5387
leaf 3 hash         = 160bd30406f8d5333be044e6d2d14624470495da8a3f91242ce338599b233931

leaf 4 version      = c0
size(leaf 4 script) = 02
leaf 4 script       = 5487
leaf 4 hash         = bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d

leaf 5 version      = c0
size(leaf 5 script) = 02
leaf 5 script       = 5587
leaf 5 hash         = 54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

branch 1 (leaf 1 hash + leaf 2 hash) = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9
branch 2 (branch 1 + leaf 3 hash)    = beec0122bddd26f642140bcd922e0264ce1e2be5808a41ae58d82e829bc913d7
branch 3 (branch 2 + leaf 4 hash)    = a4e0d9cc12ce2f32069e98247581d5eb9ca0a4cf175771a8df2c53a93dcb0ebd
branch 4 (leaf 5 hash + branch 3)    = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

NOTE: For branch 4, the leaf 5 hash goes first because it's lower than the branch 3 hash.
Tagged Hash
3. Root Hash

Keep hashing of the branches recursively until you end up with a single root hash.

This is the merkle root for your script tree.

Example

┌────────┐ ┌────────┐                
│ leaf 1 │ │ leaf 2 │                
└───┬────┘ └───┬────┘                
    └────┬─────┘                     
     ┌───┴────┐ ┌────────┐         
     │branch 1│ │ leaf 3 │         
     └───┬────┘ └─────┬──┘           
         └─────┬──────┘              
           ┌───┴────┐ ┌────────┐     
           │branch 2│ │ leaf 4 │     
           └───┬────┘ └────┬───┘     
               └────┬──────┘         
                ┌───┴────┐ ┌────────┐
                │branch 3│ │ leaf 5 │
                └───┬────┘ └────┬───┘
                    └─────┬─────┘    
                      ┌───┴────┐     
                      │branch 4│ <- root hash (aka merkle root)     
                      └────────┘

branch 1 (leaf 1 + leaf 2)   = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9
branch 2 (branch 1 + leaf 3) = beec0122bddd26f642140bcd922e0264ce1e2be5808a41ae58d82e829bc913d7
branch 3 (branch 2 + leaf 4) = a4e0d9cc12ce2f32069e98247581d5eb9ca0a4cf175771a8df2c53a93dcb0ebd
branch 4 (leaf 5 + branch 3) = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

merkle root = branch 4
merkle root = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

3. Taproot

aka tweaked public key, Taproot Output Key

After you've selected your public key and calculated the merkle root for your script tree, you now combine them to create a new tweaked public key (taproot) to place in the final P2TR locking script.

This tweaked public key is calculated in 3 steps:

1. Tweak

The first step is to calculate the tweak that will be used to tweak the public key.

This tweak is a tagged hash of the public key and the merkle root (from the previous step).

Example

public key  = a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa4
merkle root = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

tweak = tagged_hash("TapTweak", public key + merkle root)
tweak = bf0094eae70ba67e2f9fc3c4b81f078c90931855a8d24c959619174c92060cde
Tagged Hash

If you are not using a script tree, the data will just be the public key.

2. Tweaked Public Key

Next, we use this tweak to calculate a tweaked public key.

This is calculated by adding the public key point to a new point calculated from the tweak:

  1. Multiply the generator point by the tweak to create a new "tweak point".
  2. Add this "tweak point" to the original public key point to create the tweaked public key.

Example

tweak = 0xbf0094eae70ba67e2f9fc3c4b81f078c90931855a8d24c959619174c92060cde (hexadecimal)
tweak = 0d86392781870822657352762415054053103831554399693054914473067050015239558270174 (decimal)

tweak point = generator point * tweak 
tweak point = {
  x: 95269675158353859168464540318226133424394895005665805192400764661565929057235
  y: 29242344601443126469758111771751638455084106410674355391051939918836006591115
}

public key = a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa4
public key point = {
  x: 73720276170843416906336055756797472465980506800013345098392580296822094995364
  y: 11058485031460388084262061562798396269554476902102637685035851565088085936250
}

tweaked public key = tweak point + public key
tweaked public key = {
  x: 38964561412401756362079620665362329679043962000991028886256303611203090799986
  y: 63284987156862771696287631704887998797781193745849716996216065406275168637129
}

tweaked public key  = 562529047f476b9a833a5a780a75845ec32980330d76d1ac9f351dc76bce5d72 (x coordinate in hexadecimal)
EC Multiply
EC Add
Public Key

This tweaked public key will be a completely different point on the elliptic curve compared to the original public key point.

Thanks to the mathematics of elliptic curves, if we tweak the private key used to create the public key by the same value, it will actually correspond to this tweaked public key. This means we can use the tweaked private key to create a signature for the tweaked public key.

4. ScriptPubKey

Finally, we place this tweaked public key in to a locking script to create a P2TR output.

A P2TR ScriptPubKey has the following pattern:

OP_1 OP_PUSHBYTES_32 <32-byte tweaked public key>

So a P2TR locking script is just a data push of the tweaked public key, prepended with an OP_1 opcode to indicate that it's a P2TR.

Example

OP_1
OP_PUSHBYTES_32
562529047f476b9a833a5a780a75845ec32980330d76d1ac9f351dc76bce5d72
5120562529047f476b9a833a5a780a75845ec32980330d76d1ac9f351dc76bce5d72

Transaction: 0c27dceb3c5e8aeffaad5c01229eac985b4a3d56388fcef07daecb1b5482495e (Output 0) (Used for key path spend)

Transaction: 88047644c7e42421861b5d15551aa29151f86d81409fd9a3831f43a541505720 (Output 1) (Used for script path spend)

Script

Address

A P2TR ScriptPubKey can be converted to an address by converting it to Bech32m.

Example

scriptpubkey (hex) = 5120562529047f476b9a833a5a780a75845ec32980330d76d1ac9f351dc76bce5d72
address            = bc1p2cjjjprlga4e4qe6tfuq5avytmpjnqpnp4mdrtylx5wuw67wt4eqg9jscq
tool-6759b0290bfb0
Tool Icon

Address (Bech32)

Encode a P2WPKH, P2WSH, or P2TR locking script to an address.

ScriptPubKey
Version

0 bytes
0 bytes Type:
Network

Bech32 encoding of the ScriptPubKey

0 characters
0 secs

A P2TR address is always 62 characters long and starts with bc1p (mainnet). This is due to the fact the ScriptPubKey is always 34 bytes in length and starts with the byte 0x51 (OP_1).

Spending

How do you spend a P2TR?

There are two methods for spending a P2TR output:

  1. Key Path Spend — This is where you spend via the public key.
  2. Script Path Spend — This is where you spend using one of the leaf scripts in the script tree.

The unlocking code for both of these spending methods is placed inside the witness. The structure of the witness field varies depending on whether you use the key path spend or script path spend.

1. Key Path Spend

Diagram showing how to spend a P2TR output using the key path spend method

A P2TR key path spend is where you unlock the output via the default public key.

This spend is fairly straightforward, as you only need to provide a signature that corresponds to the tweaked public key in the ScriptPubKey.

A key path spend is performed in 3 steps:

1. Tweaked Private Key

To be able to create a signature that corresponds to this tweaked public key, you first need to tweak the original private key that was used to create the original public key.

The tweaked private key is calculated by simply adding the tweak to the original private key.

Example

private key         = ce1fc7baa9db31c4ef9c6564f70d551f41fc479bb23fa844d50848220edaaf91

NOTE: If the private key produces a public key with an odd y coordinate, it needs to be negated so that it produces the same public key but with an even y coordinate

n                   = fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
private key negated = n - private key
private key negated = 31e038455624ce3b10639a9b08f2aadf78b2954afd08f7f6eaca166ac15b91b0

tweak               = bf0094eae70ba67e2f9fc3c4b81f078c90931855a8d24c959619174c92060cde

tweaked private key = (private key negated + tweak) % n
tweaked private key = f0e0cd303d3074b940035e5fc111b26c0945ada0a5db448c80e32db753619e8e

Thanks to the mathematics of elliptic curves, this tweaked private key will actually correspond to the tweaked public key.

Don't lose the script tree side of the P2TR. If you've included a script tree when constructing the P2TR lock, you will need the original script tree (or at least the merkle root) to be able to calculate the tweaked private key.

2. Signature Hash

To create a signature from this tweaked private key, you first need to construct a message to be signed.

This message is called the signature hash, which is a hash of the transaction data constructed in a specific way.

See the taproot signature algorithm for details.

Example

transaction (unsigned) = 020000000001015e4982541bcbae7df0ce8f38563d4a5b98ac9e22015cadfaef8a5e3cebdc270c0000000000ffffffff014c1d0000000000001600142cfec11b4e6d86607fc072804f0c7cc5dad213d00000000000

hash type byte = 01
annex present  = 0
extension flag = 0

version            = 02000000
locktime           = 00000000
hash prevouts      = fcb2a500a85bcc659833d70ab67aca05e96403a334455c7c17a9f8e88fc56c43
hash amounts       = 8e965763e6a4bbc1088a94bf6c9cb3cbbdb4955f88355c807362a0fd43de4e3a
hash scriptpubkeys = 84fbd6ff94eb947a0cb9de10adb3485927dcfff68fce92bbc955327a27fbf166
hash sequences     = ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e
hash outputs       = c8db30b8a8c45860a0670e3036487888d35c21230f2fc37b0243d11ecf8d934f
spend type         = 00
input index        = 00000000

signature message =  hash type byte + version + locktime + hash prevouts + hash amounts + hash scriptpubkeys + hash sequences + hash outputs + spend type + input index

signature message = 010200000000000000fcb2a500a85bcc659833d70ab67aca05e96403a334455c7c17a9f8e88fc56c438e965763e6a4bbc1088a94bf6c9cb3cbbdb4955f88355c807362a0fd43de4e3a84fbd6ff94eb947a0cb9de10adb3485927dcfff68fce92bbc955327a27fbf166ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0ec8db30b8a8c45860a0670e3036487888d35c21230f2fc37b0243d11ecf8d934f0000000000

signature hash = be2cbfeb3f4e9966451179c13658ef059268f58ddda6c3708bdd51719a004d2c

The taproot signature algorithm is similar the segwit signature algorithm, but with a few minor differences.

For key path spends: extension flag = 0. This is used during the signature algorithm step.

3. Signature

The signature for a key path spend is created by signing the signature hash from the previous step using the tweaked private key.

See Schnorr signatures for details.

Example

tweaked private key = f0e0cd303d3074b940035e5fc111b26c0945ada0a5db448c80e32db753619e8e
aux rand            = 0000000000000000000000000000000000000000000000000000000000000000
message             = be2cbfeb3f4e9966451179c13658ef059268f58ddda6c3708bdd51719a004d2c

signature = schnorr_sign(tweaked private key, message, aux rand)
signature = 095282ddced815fc7130a4b78b8000b14b0205614f92958d89529afeedef26712dd452731d1721599b4e902b98017c970e51827ed287f6171234e054a61275a4

signature + hash type byte = 095282ddced815fc7130a4b78b8000b14b0205614f92958d89529afeedef26712dd452731d1721599b4e902b98017c970e51827ed287f6171234e054a61275a401
Schnorr Sign

All P2TR outputs use the Schnorr signature algorithm.

Witness

Finally, after creating the signature you just need to construct a witness field to unlock the P2TR output.

For a key path spend, the witness contains a single Schnorr signature:

<64/65-byte signature>

Example

095282ddced815fc7130a4b78b8000b14b0205614f92958d89529afeedef26712dd452731d1721599b4e902b98017c970e51827ed287f6171234e054a61275a401
{
  "stackitems": "01",
  "0": {
    "size": "41",
    "item": "095282ddced815fc7130a4b78b8000b14b0205614f92958d89529afeedef26712dd452731d1721599b4e902b98017c970e51827ed287f6171234e054a61275a401"
  },
}}
0141095282ddced815fc7130a4b78b8000b14b0205614f92958d89529afeedef26712dd452731d1721599b4e902b98017c970e51827ed287f6171234e054a61275a401

Transaction: 6f43140e371f071dafe11a73fcda9ae455f1dd9be93f2b7cc4cde21737c759a5 (Input 0)

Annex
Diagram showing the location of the annex in the witness of a key path spend and script path spend

The annex is an optional data element that can be included at the end of the witness. It is reserved for future upgrades.

It is identified by a leading 0x50 byte, and is always the last element in the witness for both a key path spend or script path spend. It gets removed from the witness before script validation.

The annex is not currently used. It should not be included when spending a P2TR output.

Set annex present = 1 during the signature algorithm step if you're including an annex in your witness.

2. Script Path Spend

This is the fun part.

Diagram showing how to spend a P2TR output using the script path spend method

A script path spend requires 3 things:

  1. A leaf script from the script tree.
  2. The inputs required to unlock that leaf script.
  3. A merkle path proving that leaf script was part of the script tree.

Each of these 3 components are then placed in to the witness. Here's how you construct each component:

1. Script Inputs

These are the stack items required to unlock your chosen leaf script.

These stack items are equivalent to the data elements you'd use to unlock any custom P2WSH locking script. These script inputs can be a variable number of elements; you just need to provide the relevant data required to unlock the leaf script.

Example

script inputs = 03

NOTE: This will unlock the simple "OP_EQUAL OP_3" upcoming leaf script

Script Path Spend Signature Algorithm

If your script inputs include a signature, the signature algorithm will be slightly different compared to a key path spend.

The 2 key differences are:

  1. You need to use extension flag = 1.
  2. You need to construct an extension to include some extra data at the end of the common signature message.

It's not a huge difference, but it's something that will probably catch you out the first time you try to make a script path spend that requires a signature.

2. Leaf Script

The next element in the witness is the complete leaf script.

This leaf script will be run in conjunction with the previous script inputs to unlock the P2TR output during script validation.

Example

leaf script = 5387 (leaf 3)

NOTE: This is the hexadecimal representation of the following script: OP_EQUAL OP_3
CAUTION: This is an insecure script and should not be used. Your coins can easily be stolen if you use it.

The script inputs + leaf script part of the witness for a P2TR script path spend is the same structure as the witness for a P2WSH.

3. Control Block

The last element in the witness is the control block.

This control block contains the merkle path that proves the previous leaf script was part of the original script tree. It also contains the original public key, which is required to verify that the public key and merkle root can be combined to match the tweaked public key in the ScriptPubKey.

Example

control byte  = c1
public key    = a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa4
merkle path   = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

control block = <control byte> <public key> <merkle path>
control block = c1a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa41324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

The complete control block is split into 3 sections:

1. Control Byte

This first byte of the control block is the control byte. This encodes two pieces of data:

  1. The leaf version of your chosen leaf script.
  2. A bit indicating the parity of the y-coordinate of the full tweaked public key point.
    • even = 0
    • odd = 1

To create the control byte, you just add the leaf version and parity bit.

Example

leaf version = c0 (leaf version of the leaf script we are using i.e. leaf 3)
parity bit   = 1 (y coordinate of tweaked public key is odd)

control byte = leaf version + parity bit
control byte = c1
2. Public Key

The next 32 bytes of the control block contains the original public key.

This needs to be revealed so that the tweaked public key can be calculated from it.

Example

public key = a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa4
3. Merkle Path

The remaining data in the control block is the merkle path.

This is a series of the 32-byte leaf/branch hashes required to construct the merkle root. The relevant leaf/branch hashes must be placed in order, so that anyone else can construct the same merkle root.

This merkle path proves that your leaf script was part of the original script tree, as the combination of the previous public key and the resulting merkle root will match the tweaked public key placed in the ScriptPubKey.

Example

┌────────┐ ┌────────┐                
│ leaf 1 │ │ leaf 2 │                
└───┬────┘ └───┬────┘                
    └────┬─────┘                     
     X───┴────┐ ┌────────┐         
     │branch 1│ │ leaf 3 │ <- spending           
     └───┬────┘ └─────┬──┘           
         └─────┬──────┘              
           ┌───┴────┐ X────────┐     
           │branch 2│ │ leaf 4 │     
           └───┬────┘ └────┬───┘     
               └────┬──────┘         
                ┌───┴────┐ X────────┐
                │branch 3│ │ leaf 5 │
                └───┬────┘ └────┬───┘
                    └─────┬─────┘    
                      ┌───┴────┐     
                      │branch 4│     
                      └────────┘   Note: X = hashes required to construct the merkle path


leaf 1 hash  = 6b13becdaf0eee497e2f304adcfa1c0c9e84561c9989b7f2b5fc39f5f90a60f6
leaf 2 hash  = ed5af8352e2a54cce8d3ea326beb7907efa850bdfe3711cef9060c7bb5bcf59e
leaf 3 hash  = 160bd30406f8d5333be044e6d2d14624470495da8a3f91242ce338599b233931
leaf 4 hash  = bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d
leaf 5 hash  = 54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

branch 1     = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9
branch 2     = beec0122bddd26f642140bcd922e0264ce1e2be5808a41ae58d82e829bc913d7
branch 3     = a4e0d9cc12ce2f32069e98247581d5eb9ca0a4cf175771a8df2c53a93dcb0ebd
branch 4     = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

merkle path  = branch 1 + leaf 4 hash + leaf 5 hash
merkle path  = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

The merkle path contains the minimum number of leaf/branch hashes required to reconstruct the merkle root. The closer your leaf script is to the merkle root, the shorter your merkle path will be.

The merkle path is limited to 128 leaf/branch hashes.

Witness

The witness for a script path spend contains the script inputs, leaf script, and control block

<script inputs...> <leaf script> <control block>

Example

03
5387
c1a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa41324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f
{
  "stackitems": "03",
  "0": {
    "size": "01",
    "item": "03"
  },
  "1": {
    "size": "02",
    "item": "5387"
  },
  "2": {
    "size": "81",
    "item": "c1a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa41324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f"
  }
}
03010302538781c1a2fc329a085d8cfc4fa28795993d7b666cee024e94c40115141b8e9be4a29fa41324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

Transaction: fa7eb13f6d854ed32ef284983c620f74050dd6d119dc9e91ad09c083b0267f8f (Input 1)

The size of the witness for a script path spend will vary depending on:

  1. The size of the leaf script.
  2. How many script inputs are required (i.e. the complexity of the leaf script).
  3. How far up the leaf script was up the script tree (i.e. how many leaf/branch hashes need to be provided in the control block).

The fact that the witness contains more than 1 element is what identifies it as a script path spend.

Signature Algorithm

How do you create a signature for a P2TR?

Diagram showing a summary of how to create a signature for a P2TR output using the taproot signature algorithm

P2TR outputs use a special signature algorithm for both key path spends and script path spends.

Instead of using the entire raw transaction data as it is (as you do in the legacy algorithm), you collect specific parts in to a common signature message structure first.

The benefit of this is parts of the common signature message can be re-used when creating further signatures for the same transaction. This makes it more efficient when creating multiple signatures to unlock multiple inputs in the same transaction.

This signature algorithm is similar to segwit algorithm. It just has some small adjustments and includes some extra taproot-specific data for signing.

See the key path spend example and script path spend (signature) example for actual code examples.

Common Signature Message

The common signature message holds the bulk of the transaction data.

The parts of the transaction data included in this structure varies depending on how much of the transaction data you're signing (as specified by the hash type).

Field Size (bytes) Description
Hash Type 1 A byte indicating the amount of the transaction data you want to sign.
Version 4 The transaction version field.
Locktime 4 The transaction locktime field.
Hash Prevouts 32 The SHA-256 hash of the txid+vout outpoints for all the inputs included in the transaction.
Hash Amounts 32 The SHA-256 hash of all the output amount fields for all the inputs included in the transaction.
Hash ScriptPubKeys 32 The SHA-256 hash all the output scriptpubkeys for all the inputs included in the transaction.
Note: The data for each scriptpubkey is scriptpubkeysize+scriptpubkey.
Hash Sequences 32 The SHA-256 hash of all the sequence fields for all the inputs included in the transaction.
Hash Outputs 32 The SHA-256 hash of all the outputs in the transaction.
Note: The data for each output is amount+scriptpubkeysize+scriptpubkey.
Spend Type 1 A single byte that encodes the extension flag and annex present values.
Input Index 4 The vin of the input being signed for.
Input Outpoint 36 The txid+vout outpoint of the input being signed for.
Input Amount 8 The amount field of the input being signed for.
Input ScriptPubKey variable The scriptpubkey of the input being signed for.
Input Sequence 4 The sequence field of the input being signed for.
Hash Annex 32 The SHA-256 of the optional annex included at the end of the witness field.
Note: The annex must have a compact size field prefixed before hashing.
Hash Single Output 32 The SHA-256 of the output opposite the input currently being signed for.
Note: The data for the output is amount+scriptpubkeysize+scriptpubkey

Key:

Hash Type

The hash type byte indicates how much of the transaction you want to sign.

The amount of the transaction you sign determines whether other people can add or remove inputs and outputs from your signed transaction.

Byte Type Description
0x01 SIGHASH_ALL Sign all inputs and outputs
0x02 SIGHASH_NONE Sign all inputs only
0x03 SIGHASH_SINGLE Sign all inputs and one corresponding output
0x81 SIGHASH_ANYONECANPAY | SIGHASH_ALL Sign one input and all outputs
0x82 SIGHASH_ANYONECANPAY | SIGHASH_NONE Sign one input only
0x83 SIGHASH_ANYONECANPAY | SIGHASH_SINGLE Sign one inputs and one corresponding output

See signature hash types for more details.

Default Hash Type

Taproot introduces a new default SIGHASH byte for use in P2TR:

0x00 = SIGHASH_DEFAULT

This default reverts to SIGHASH_ALL, which is the most common hash type as it signs the entire transaction data (preventing anyone from making any changes to the signed transaction).

SIGHASH_DEFAULT is also used if the no hash type byte is appended to the final signature. So if you're signing a transaction using SIGHASH_DEFAULT (i.e. SIGHASH_ALL), the hash type byte at the end of the signature is optional.

Spend Type

The spend type encodes the extension flag and annex present values in to a single byte.

spend type = (extension flag * 2) + annex present

Extension Flag

The extension flag indicates whether an extension has been added to the end of the common signature message.

An extension is only used if you're creating a signature for a script path spend.

All leaf scripts currently use tapscript, so the extension flag for all script path spends should be 1.

Annex Present

The annex present value indicates whether the optional annex element has been included at the end of the witness field.

The annex is not currently used, so the annex present value should be 0.

Common Signature Message Extension

Diagram showing how to construct the common signature message extension from the chosen leaf in the script tree

Script path spends require an extension to the common signature message.

This extension contains some extra information about the chosen leaf script:

Field Size (bytes) Description
Leaf Hash 32 The leaf hash for the chosen script you're using from the script tree.
Public Key Version 1 The type of public key used in the leaf script. Used indicate different types of public keys in future upgrades.
default = 0x00
Codeseparator Position 4 The opcode position of the last OP_CODESEPARATOR in the leaf script (if there is one).
none = 0xffffffff

Unless you have a reason to change them, for most leaf scripts the public key version = 0x00 and codeseparator position = 0xffffffff.

Signature Hash

The signature hash is the actual "message" that we sign when creating the signature.

It's a tagged hash of the common signature message, along with a sighash epoch prefix and the optional extension:

Sighash Epoch

0x00 = default

The sighash epoch byte is prefixed to the common signature message when creating the signature hash.

It's an extra byte that allows for upgrades to the signature algorithm in the future. Using this prefix means that the "TapSigHash" can be reused without having to use different tags to indicate different algorithm changes in the future.

Just use 0x00 and don't worry about it for now.

Signature

The signature for P2TR spends is created using the Schnorr signature scheme. This involves signing the signature hash from above using a private key.

signature = schorr_sign(private key, message, aux rand)

This returns a signature that can be used inside the witness to unlock the P2TR output.

The actual signature that gets placed inside the witness has the hash type byte appended to the end. If you do not append a hash type byte to the signature, SIGHASH_DEFAULT is used.

Tapscript

Diagram showing the location of a tapscript script in the leaf script and the witness

Tapscript is the locking mechanism used in leaf scripts.

It's basically the same as Script.

The only difference is that some opcodes have been modified to use Schnorr signature validation instead of ECDSA, and a handful of other opcodes have been modified or disabled. But for the most part, it works the same as traditional Script.

The version of tapscript is indicated by the leaf version, which allows for new variations of tapscript in the future.

Opcodes

Tapscript changes the operation of the following opcodes:

Byte Opcode Description
0xac OP_CHECKSIG Now uses Schnorr signature verification with the taproot signature algorithm.
0xad OP_CHECKSIGVERIFY Now uses Schnorr signature verification with the taproot signature algorithm.
0xae OP_CHECKMULTISIG Disabled.
0xaf OP_CHECKMULTISIGVERIFY Disabled.
0xba OP_CHECKSIGADD New. Replaces OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY. Allows for more efficient multisignature scripts using batch verification.
OP_NOPX Renamed to OP_SUCCESSX.

These are reserved for upgrades requiring new opcode functionality. These opcodes were previously disabled, but in tapscript (version 192) the presence of these opcodes mark the script as valid (no matter where they appear).

OP_NOPX refers to the following opcodes: 0x50, 0x62, 0x7e-0x81, 0x83-0x86, 0x89-0x8a, 0x8d-0x8e, 0x95-0x99, 0xbb-0xfe.

Limits

Diagram showing the size limits of tapscript scripts

Tapscript has increased limits compared to Script. There are two significant changes:

This means that the size of leaf scripts are now only limited by the size of a block.

Sigops Budget

Tapscript uses a "budget" to limit the number of signature operations (i.e. OP_CHECKSIG, OP_CHECKSIGVERIFY) in a single script.

Budget              = 50 + witness size
Signature Operation = -50

This budget means that every tapscript can include at least 1 signature operation, but can include more depending on the size of leaf script and the complexity of the script tree (as this will influence the size of the control block).

Legacy Sigops Limit

This sigops budget means that the sigops limit for tapscript is now per-script as opposed to per-block. This makes it easier for miners to select transactions for inclusion in their blocks.

The legacy sigops limit acts as a secondary constraint in addition to the block size limit, so miners have to be extra cautious about which transactions they include in their block.

Examples

Here are some actual examples showing the construction and spending of P2TR outputs. They show raw data and code for the most common ways to create and spend P2TR locking scripts.

These examples are basic, but they're ideal if you're coding your own implementation and want to check that the results you're getting along the way are correct.

Common Code

This is a file of common helper functions used in all the code examples below. You can save it as examples-taproot-common.rb in the same directory as the code examples below, or copy and paste the code at the top of each one.

# -------------------------
# Elliptic Curve Parameters
# -------------------------
# these are the parameters for secp256k1, which is the same curve used in ECDSA
# note: setting these as $global variables so they're accessible from with the functions below (without having to pass them as arguments)

# y² = x³ + ax + b
$a = 0
$b = 7

# prime field
$p = 115792089237316195423570985008687907853269984665640564039457584007908834671663 #=> 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

# number of points on the curve we can hit ("order")
$n = 115792089237316195423570985008687907852837564279074904382605163141518161494337 #=> 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

# generator point (the starting point on the curve used for all calculations)
$G = {
  x: 55066263022277343669578718895168534326250603453777594175500187360389116729240, #=> 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
  y: 32670510020758816978083085130507043184471273380659243275938904335757337482424, #=> 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
}

# --------------------------
# Elliptic Curve Mathematics
# --------------------------

# Modular Inverse: Ruby doesn't have a built-in modinv function
def inverse(a, m = $p)
  m_orig = m         # store original modulus
  a = a % m if a < 0 # make sure a is positive
  y_prev = 0
  y = 1
  while a > 1
    q = m / a

    y_before = y # store current value of y
    y = y_prev - q * y # calculate new value of y
    y_prev = y_before # set previous y value to the old y value

    a_before = a # store current value of a
    a = m % a # calculate new value of a
    m = a_before # set m to the old a value
  end
  return y % m_orig
end

# Double: add a point to itself
def double(point)
  # check for point at infinity (greatest common divisor between 2y and p isn't 1)
  if (((2 * point[:y]) % $p).gcd($p) != 1) # taken from BitcoinECDSA.php
    raise "Point at infinity."
  end

  # slope = (3x₁² + a) / 2y₁
  slope = ((3 * point[:x] ** 2 + $a) * inverse((2 * point[:y]), $p)) % $p # using inverse to help with division

  # x = slope² - 2x₁
  x = (slope ** 2 - (2 * point[:x])) % $p

  # y = slope * (x₁ - x) - y₁
  y = (slope * (point[:x] - x) - point[:y]) % $p

  # Return the new point¢ªº
  return { x: x, y: y }
end

# Add: add two points together
def add(point1, point2)
  # double if both points are the same
  if point1 == point2
    return double(point1)
  end

  # check for point at infinity (greatest common divisor between x1-x2 and p isn't 1)
  if ((point1[:x] - point2[:x]).gcd($p) != 1) # taken from BitcoinECDSA.php
    raise "Point at infinity."
  end

  # slope = (y₁ - y₂) / (x₁ - x₂)
  slope = ((point1[:y] - point2[:y]) * inverse(point1[:x] - point2[:x], $p)) % $p

  # x = slope² - x₁ - x₂
  x = (slope ** 2 - point1[:x] - point2[:x]) % $p

  # y = slope * (x₁ - x) - y₁
  y = ((slope * (point1[:x] - x)) - point1[:y]) % $p

  # Return the new point
  return { x: x, y: y }
end

# Multiply: use double and add operations to quickly multiply a point by an integer value (i.e. a private key)
def multiply(k, point = $G)
  # create a copy the initial starting point (for use in addition later on)
  current = point

  # convert integer to binary representation
  binary = k.to_s(2)

  # double and add algorithm for fast multiplication
  binary.split("").drop(1).each do |char| # from left to right, ignoring first binary character
    # 0 = double
    current = double(current)

    # 1 = double and add
    current = add(current, point) if char == "1"
  end

  # return the final point
  current
end

# ----------------
# BIP340 Functions (Schnorr Signatures)
# ----------------

# convert hexadecimal string of bytes to integer
def int(bytes)
  return bytes.to_i(16)
end

# convert integer to hexadecimal string of bytes
def bytes(int)
  return int.to_s(16).rjust(64, "0") # convert to hex and pad with zeros to make it 32 bytes (64 characters)
end

# hash some data using SHA256 with a tag prefix
def tagged_hash(tag, message)

  # create a hash of the tag first
  tag_hash = Digest::SHA256.hexdigest(tag) # hash the string directly

  # prefix the message with the tag hash (the tag_hash is prefixed twice so that the prefix is 64 bytes in total)
  preimage = [tag_hash + tag_hash + message].pack("H*") # also convert to byte sequence before hashing

  # SHA256(tag_hash || tag_hash || message)
  result = Digest::SHA256.hexdigest(preimage);

  return result
end

# convert public key (x coordinate only) in to a point - lift_x() in BIP 340
def lift_x(public_key)
  x = int(public_key) # convert from x coordinate from hex to an integer
  y_sq = (x**3 + 7) % $p # use the elliptic curve equation (y² = x³ + ax + b) to work out the value of y from x
  y = y_sq.pow(($p+1)/4, $p) # secp256k1 is chosen in a special way so that the square root of y is y^((p+1)/4)

  # check that x coordinate is less than the field size
  if x >= $p
    raise "x value in public key is not a valid coordinate because it is not less than the elliptic curve field size"
  end

  # verify that the computed y value is the square root of y_sq (otherwise the public key was not a valid x coordinate on the curve)
  if (y**2) % $p != y_sq
    raise "public key is not a valid x coordinate on the curve"
  end

  # if the calculated y value is odd, negate it to get the even y value instead (for this x-coordinate)
  if y % 2 != 0
    y = $p - y
  end

  # public key point
  public_key_point = {x: x, y: y}

  return public_key_point
end

# ----------------
# BIP341 Functions (Taproot)
# ----------------

# calculate control byte from leaf version and parity of tweaked public key
def calculate_control_byte(leaf_version, tweaked_pubkey_point)
	
	# set parity bit based on whether y is even or odd
	if (tweaked_pubkey_point[:y] % 2 == 0)
		parity_bit = 0 # y is even
	else
		parity_bit = 1 # y is odd
	end
  
  # Why is it necessary to reveal a bit in a script path spend and check that it matches the parity of the Y coordinate of Q?
  # The parity of the Y coordinate is necessary to lift the X coordinate q to a unique point. While this is not strictly necessary for verifying the taproot commitment as described above, it is necessary to allow batch verification. Alternatively, Q could be forced to have an even Y coordinate, but that would require retrying with different internal public keys (or different messages) until Q has that property. There is no downside to adding the parity bit because otherwise the control block bit would be unused.
	
	# calculate control byte
	control_byte = field(dechex(leaf_version + parity_bit), 1)
	
	return control_byte
end

# -----------------
# General Functions
# -----------------

# convert decimal number to hexadecimal
def dechex(dec)
  return dec.to_i.to_s(16)
end

# add padding to create a fixed-size field (e.g. 4 => 00000004)
def field(field, size=4)
  return field.to_s.rjust(size*2, '0')
end

# swap endianness
def reversebytes(hex)
  return hex.to_s.scan(/../).reverse.join
end

# get length of bytes in compact size structure
def compact_size(i)
  if (i <= 252)
    result = field(dechex(i), 1)
  elsif (i > 252 && i <= 65535)
    result = 'fd' + field(dechex(i), 2)
  elsif (i > 65535  && i <= 4294967295)
    result = 'fe' + field(dechex(i), 4)
  elsif (i > 4294967295 && i <= 18446744073709551615)
    result = 'ff' + field(dechex(i), 8)
  end

  return result
end

# add compact size field to start of scriptpubkey
def serialize_script(script)
  # get length of script as number of bytes
  length = script.length / 2
  
  # return script with compact size prepended
  return compact_size(length) + script
end

1. Key Path Spend

This is an example of a key path spend.

This is the "default" method for spending a P2TR output.

Construct

public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329
merkle root         =

tweak               = 8dc8b9030225e044083511759b58328b46dffcc78b920b4b97169f9d7b43d3b5
tweaked public key  = 0f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667

scriptpubkey        = 51200f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667

TXID: a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec (Output 0)

This example doesn't use a script tree, which is why the merkle root is empty.

Spend

private key         = 55d7c5a9ce3d2b15a62434d01205f3e59077d51316f5c20628b3a4b8b2a76f4c
public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329
merkle root         = 

tweak               = 8dc8b9030225e044083511759b58328b46dffcc78b920b4b97169f9d7b43d3b5

tweaked private key = 37f0f35933e8b52e6210dca589523ea5b66827b4749c49456e62fae4c89c6469
sighash             = a7b390196945d71549a2454f0185ece1b47c56873cf41789d78926852c355132
aux rand            = 0000000000000000000000000000000000000000000000000000000000000000

signature           = b693a0797b24bae12ed0516a2f5ba765618dca89b75e498ba5b745b71644362298a45ca39230d10a02ee6290a91cebf9839600f7e35158a447ea182ea0e022ae

witness             = b693a0797b24bae12ed0516a2f5ba765618dca89b75e498ba5b745b71644362298a45ca39230d10a02ee6290a91cebf9839600f7e35158a447ea182ea0e022ae01

TXID: 091d2aaadc409298fd8353a4cd94c319481a0b4623fb00872fe240448e93fcbe (Input 0)

This example data does not show the construction of the sighash from the original unsigned transaction data. See the code below for the complete steps and data.

Code

This code requires examples-taproot-common.rb

require_relative 'examples-taproot-common.rb' # common functions
require "digest" # library for SHA256 hash function

# =========
# Construct
# =========

# ----------
# Public Key
# ----------

internal_pubkey = '924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329'

# -----------
# Merkle Tree
# -----------

# merkle root
merkle_root = ""

# ----------------
# Tweak Public Key
# ----------------

# create a hash from the public key and merkle root - this will be used to tweak the public key
tweak = tagged_hash("TapTweak", internal_pubkey + merkle_root)
#=> 8dc8b9030225e044083511759b58328b46dffcc78b920b4b97169f9d7b43d3b5

# tweak the public key
t = int(tweak) # convert to integer (so it's like a private key) (check this is less than curve order)
internal_pubkey_point = lift_x(internal_pubkey)
tweaked_pubkey_point = add(internal_pubkey_point, multiply(t, $G)) # (check y is even)
tweaked_pubkey = bytes(tweaked_pubkey_point[:x])
#=> 0f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667

# convert to scriptpubkey
scriptpubkey = '51' + '20' + tweaked_pubkey
#=> 51200f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667


# =====
# Spend
# =====

# -------------------
# Signature Algorithm
# -------------------

hash_type = 1 # SIGHASH_ALL
hash_type_byte = field(dechex(hash_type), 1) # specific hash type being used for this input (1 = SIGHASH_ALL)

ext_flag = 0
annex_present = 0 # 1 = true, 0 = false
spend_type = field(dechex((ext_flag * 2) + annex_present), 1)

# unsigned raw tx:
#
# 02000000000101ec9016580d98a93909faf9d2f431e74f781b438d81372bb6aab4db67725c11a70000000000ffffffff0110270000000000001600144e44ca792ce545acba99d41304460dd1f53be3840000000000

version = '02000000'
locktime = '00000000'

prevouts = 'ec9016580d98a93909faf9d2f431e74f781b438d81372bb6aab4db67725c11a7'+'00000000' # txid and amount are in little-endian
amounts = reversebytes(field(dechex('20000'), 8))
sequences = 'ffffffff'
scriptpubkeys = serialize_script('51200f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667')

sha_prevouts = Digest::SHA256.hexdigest([prevouts].pack("H*"));           #=> eaff979f4771d11a857e48550a28c4d3503cf2a966182c94010fd21d5b700700
sha_amounts = Digest::SHA256.hexdigest([amounts].pack("H*"));             #=> ae9475d31b535bec000c9bfc7abc79b6a07db9eea2dd0e5066adddfb349bb53b
sha_sequences = Digest::SHA256.hexdigest([sequences].pack("H*"));         #=> ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e
sha_scriptpubkeys = Digest::SHA256.hexdigest([scriptpubkeys].pack("H*")); #=> 4cd686f794463476c6fc24b4a43e0abc7b58a0ea78a998d2be39cdb73f8d9cc2

outputs = '1027000000000000' + serialize_script('00144e44ca792ce545acba99d41304460dd1f53be384') # amount + scriptpubkey
sha_outputs = Digest::SHA256.hexdigest([outputs].pack("H*")); #=> c3a3f98ac2310126a614269e5715b0cabf38ce62232dd9ed8a878bdc0addea75

input_index = '00000000' # 4-byte vin of the input you're spending

sha_annex = ''

sha_single_output = ''

# signature message
sigmsg = ''
sigmsg += hash_type_byte + version + locktime

# NOT SIGHASH_ANYONECANPAY (0x80)
if (hash_type & 0x80 == 0)
  sigmsg += sha_prevouts + sha_amounts + sha_scriptpubkeys + sha_sequences
end

# NOT SIGHASH_NONE (0x02/0x82) OR SIGHASH_SINGLE (0x03/0x83)
if (hash_type & 3 < 2)
  sigmsg += sha_outputs
end

sigmsg += spend_type

# NOT SIGHASH_ANYONECANPAY (0x80)
if (hash_type & 0x80 == 0)
  sigmsg += input_index

# SIGHASH_ANYONECANPAY (0x80)
else
  sigmsg += input_outpoint + input_amount + input_scriptpubkey + input_sequence
end

sigmsg += sha_annex

# SIGHASH_SINGLE
if (hash_type & 3 == 3)
  sigmsg += sha_single_output
end

#=> 010200000000000000eaff979f4771d11a857e48550a28c4d3503cf2a966182c94010fd21d5b700700ae9475d31b535bec000c9bfc7abc79b6a07db9eea2dd0e5066adddfb349bb53b4cd686f794463476c6fc24b4a43e0abc7b58a0ea78a998d2be39cdb73f8d9cc2ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0ec3a3f98ac2310126a614269e5715b0cabf38ce62232dd9ed8a878bdc0addea750000000000

# sighash epoch
epoch = '00'

# sighash
sighash = tagged_hash("TapSighash", epoch + sigmsg) # don't forget the 0x00 prefix to the sigmsg
#=> a7b390196945d71549a2454f0185ece1b47c56873cf41789d78926852c355132


# -----------------
# Tweak Private Key
# -----------------

# original data from before
internal_pubkey = '924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329'
tweak = '8dc8b9030225e044083511759b58328b46dffcc78b920b4b97169f9d7b43d3b5'
private_key = '55d7c5a9ce3d2b15a62434d01205f3e59077d51316f5c20628b3a4b8b2a76f4c'

# convert private key to the public key point so we can check if it creates an even y value
private_key_int = int(private_key) #=> 38827828470485795394567956987183954348973858899545806359243020977513867734860
private_key_to_public_key = multiply(private_key_int)
# {:x=>66172109705071441823295681989107852967180089637640153745774876919271983297321, :y=>48613218598235331436749946747294004934275959149063298181052452063566809595043}
# x = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329 (same as internal public key)

# negate the private key if y is odd
if private_key_to_public_key[:y] % 2 == 1
  private_key_int_negated = $n - private_key_int #=> 76964260766830400029003028021503953503863705379529098023362142164004293759477
else
  private_key_int_negated = private_key_int
end

tweaked_privkey_int = (private_key_int_negated + int(tweak)) % $n
tweaked_privkey = bytes(tweaked_privkey_int)
#=> 37f0f35933e8b52e6210dca589523ea5b66827b4749c49456e62fae4c89c6469

# ----
# Sign
# ----

# signing data
private_key = tweaked_privkey
message = sighash
aux_rand = '0000000000000000000000000000000000000000000000000000000000000000'

# convert private key to an integer
d0 = int(private_key)

# make sure private key is in valid range (greater than 0 and less than the number of points on the curve)
unless (1..$n-1).include?(d0)
  raise "private key must be in the range 1..n-1"
end

# calculate the public key point from the private key
public_key_point = multiply(d0) # multiply() checks for point at infinity

# negate the private key if the public key it creates doesn't have an even y value, else keep the private key the same
# note: due to the way the elliptic curve works, negate the private key will produce a public key with the same x coordinate, but the opposite y value
if public_key_point[:y] % 2 != 0
  d = $n - d0
else
  d = d0
end

# create a tagged hash of the auxiliary bytes
aux_rand_hash = tagged_hash("BIP0340/aux", aux_rand)

# first step toward creating the nonce is to XOR the private key with the hash of the auxiliary bytes
t = d ^ int(aux_rand_hash)

# create the nonce by hashing t (from the previous step) along with the public_key and message
k0 = int(tagged_hash("BIP0340/nonce", bytes(t) + bytes(public_key_point[:x]) + message)) % $n # public key is included in hash for "key-prefixed" schnorr signatures

# check that the nonce isn't zero
if k0 == 0
  raise "nonce must not be zero (this is almost impossible, but checking anyway)"
end

# use this nonce to get a point on the curve
random_point = multiply(k0) # multiply() checks for point at infinity

# negate the nonce used to create the random point if the public key it creates doesn't have an even y value
if random_point[:y] % 2 != 0
  k = $n - k0
  # note: due to the way the elliptic curve works, the inverse private key will produce an even y value
else
  k = k0
end

# create the challenge e value by hashing the random point with the public key and message
e = int(tagged_hash("BIP0340/challenge", bytes(random_point[:x]) + bytes(public_key_point[:x]) + message)) % $n

# r value is the x-coordinate of point R
r =  random_point[:x]

# s value: (k + e*d) mod n
s = (k + e * d) % $n # this is linear (whereas s in ECDSA is non-linear)

# signature is the r and s values converted to 32-byte hexadecimal string and concatenated
sig = bytes(r) + bytes(s)
#=> b693a0797b24bae12ed0516a2f5ba765618dca89b75e498ba5b745b71644362298a45ca39230d10a02ee6290a91cebf9839600f7e35158a447ea182ea0e022ae

# -------
# Witness
# -------

# witness (signature + hash_type byte)
witness = sig + hash_type_byte

puts witness
#=> b693a0797b24bae12ed0516a2f5ba765618dca89b75e498ba5b745b71644362298a45ca39230d10a02ee6290a91cebf9839600f7e35158a447ea182ea0e022ae01

2. Script Path Spend (Simple)

This is an example of a basic script path spend.

The script tree contains one leaf. The leaf script is a basic OP_8 OP_EQUAL script that does not require a signature.

It's easier to get to grips with script path spends the first time around by using a leaf script that does not require a signature.

This script is not secure. I'm just using this basic leaf script to highlight the mechanics of a script path spend. If you use this kind of script in the real world your coins are likely to be stolen when you broadcast the spending transaction (which is actually what happened to me when I spent this output).

Construct

public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

leaf version        = c0
leaf script         = 5887
leaf hash           = e4b47d76d2f78791323a035811c350bb7875568006cb60f0e171efb70c11bda4
merkle root         = e4b47d76d2f78791323a035811c350bb7875568006cb60f0e171efb70c11bda4

tweak               = fe9007dc134942f8aabcc1af9e1c201c454d333501efccfbb8abf05bdfcb09a5
tweaked public key  = 1baeaaf9047cc42055a37a3ac981bdf7f5ab96fad0d2d07c54608e8a181b9477

scriptpubkey        = 51201baeaaf9047cc42055a37a3ac981bdf7f5ab96fad0d2d07c54608e8a181b9477

TXID: 8bc4f8facaaf7c4bdf6d77fac90aea208c2099a091d4b09658d002739daaad87 (Output 1)

This script tree only contains one leaf, so the resulting merkle root is equal to that leaf hash.

Spend

script inputs       = 08
leaf script         = 5887

control byte        = c1
public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329
merkle path         =
control block       = c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

witness             = 03010802588721c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

TXID: 5ff05f74d385bd39e344329330461f74b390c1b5ead87c4f51b40c555b75719d (Input 1)

The script input is 08, which satisfies the original leaf script of OP_8 OP_EQUAL.

No merkle path is required to spend using this leaf script, because the script tree only contained one leaf and merkle root can be reconstructed from the single leaf script alone.

The resulting witness is the witness encoding of the following items: script inputs + leaf script + control block.

The control byte is c1 instead of c0, as the parity of the tweaked public key is odd.

Code

This code requires examples-taproot-common.rb

require_relative 'examples-taproot-common.rb' # common functions
require "digest" # library for SHA256 hash function

# =========
# Construct
# =========

# ----------
# Public Key
# ----------

internal_pubkey = '924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329'

# -----------
# Merkle Tree
# -----------

# leaf
leaf_version = 192 # 0xc0
script = '5887' # OP_8 OP_EQUAL
leaf_hash = tagged_hash("TapLeaf", field(dechex(leaf_version), 1) + serialize_script(script))
#=> e4b47d76d2f78791323a035811c350bb7875568006cb60f0e171efb70c11bda4

# merkle root
merkle_root = leaf_hash

# ----------------
# Tweak Public Key
# ----------------

# create a hash from the public key and merkle root - this will be used to tweak the public key
tweak = tagged_hash("TapTweak", internal_pubkey + merkle_root)
#=> fe9007dc134942f8aabcc1af9e1c201c454d333501efccfbb8abf05bdfcb09a5

# tweak the public key
t = int(tweak) # convert to integer (so it's like a private key) (check this is less than curve order)
internal_pubkey_point = lift_x(internal_pubkey)
tweaked_pubkey_point = add(internal_pubkey_point, multiply(t, $G)) # (check y is even)
tweaked_pubkey = bytes(tweaked_pubkey_point[:x])
#=> 1baeaaf9047cc42055a37a3ac981bdf7f5ab96fad0d2d07c54608e8a181b9477

# convert to scriptpubkey
scriptpubkey = '51' + '20' + tweaked_pubkey
#=> 51201baeaaf9047cc42055a37a3ac981bdf7f5ab96fad0d2d07c54608e8a181b9477


# =====
# Spend
# =====

# -------------
# Control Block
# -------------

# no merkle path required, as the leaf hash is the merkle root
merkle_path = ''

# note: all leaf_versions are 192
control_byte = calculate_control_byte(192, tweaked_pubkey_point)

# control block (no branches or leaves are needed to build the merkle root - the hash of the script is the merkle root)
control_block = control_byte + internal_pubkey
puts control_block
#=> c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

# -------
# Witness
# -------
# <script inputs> <script> <control block>

script_inputs = '08'   # CAUTION: The witness only contains data pushes, unlike the scriptSig.
script        = '5887' # OP_8 OP_EQUAL
control_block = control_block

# NOTE: The witness only contains data pushes, unlike the scriptSig.
# If you try to set 58 (OP_8) as the script input, the Script interpreter compares 0x58 and 8 and pushes 0 on the stack.
# You will then get this error:
#   mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)

witness = 
  compact_size(3) + 
  compact_size(script_inputs.length / 2) + script_inputs + 
  compact_size(script.length / 2) + script + 
  compact_size(control_block.length / 2) + control_block

puts witness
#=> 03015802588721c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

3. Script Path Spend (Signature)

This is another example of a script path spend, but this time the leaf script requires a signature to unlock it.

This means you need to use the signature algorithm for script path spends, which is slightly different to the algorithm used in key path spends.

Construct

public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

leaf version        = c0
leaf script         = 206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac
leaf hash           = 858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16
merkle root         = 858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16

tweak               = 479785dd89a6441dbe00c7661865a0cc68672e8021f4547ac7f89ac26ac049f2
tweaked public key  = f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80

scriptpubkey        = 5120f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80

TXID: d1c40446c65456a9b11a9dddede31ee34b8d3df83788d98f690225d2958bfe3c (Output 0)

Spend

script private key  = 9b8de5d7f20a8ebb026a82babac3aa47a008debbfde5348962b2c46520bd5189
sighash             = 752453d473e511a0da2097d664d69fe5eb89d8d9d00eab924b42fc0801a980c9
aux rand            = 0000000000000000000000000000000000000000000000000000000000000000
signature           = 01769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f01

leaf script         = 206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac

control byte        = c0
public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329
merkle path         =
control block       = c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

witness             = 034101769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f0122206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac21c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

TXID: 797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2 (Input 0)

To create a signature to unlock this leaf script, you need to use the private key for the public key in the leaf script (and not the one for the public key used in a key path spend).

The resulting witness is the witness encoding of the following items: signature + leaf script + control block.

This example data does not show the construction of the sighash from the original unsigned transaction data. See the code below for the complete steps and data.

Code

This code requires examples-taproot-common.rb

require_relative 'examples-taproot-common.rb' # common functions
require "digest" # library for SHA256 hash function

# =========
# Construct
# =========

# ----------
# Public Key
# ----------

internal_pubkey = '924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329'

# -----------
# Merkle Tree
# -----------

# hash leaf
leaf_version = 192 # 0xc0
script = '206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac' # OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
leaf_hash = tagged_hash("TapLeaf", field(dechex(leaf_version), 1) + serialize_script(script))
#=> 858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16

# merkle root
merkle_root = leaf_hash

# ----------------
# Tweak Public Key
# ----------------

# create a hash from the public key and merkle root - this will be used to tweak the public key
tweak = tagged_hash("TapTweak", internal_pubkey + merkle_root) 
#=> 479785dd89a6441dbe00c7661865a0cc68672e8021f4547ac7f89ac26ac049f2

# tweak the public key
t = int(tweak) # convert to integer (so it's like a private key) (check this is less than curve order)
internal_pubkey_point = lift_x(internal_pubkey)
tweaked_pubkey_point = add(internal_pubkey_point, multiply(t, $G)) # (check y is even)
tweaked_pubkey = bytes(tweaked_pubkey_point[:x])
#=> f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80

# convert to scriptpubkey
scriptpubkey = '51' + '20' + tweaked_pubkey
#=> 5120f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80


# =====
# Spend
# =====

# -------------
# Control Block
# -------------
# <control byte with leaf version and parity bit> <internal pubkey> <leaves/branches required to calculate merkle root>

# note: all leaf_versions are 192
control_byte = calculate_control_byte(192, tweaked_pubkey_point)

# control block (no branches or leaves are needed to build the merkle root - the hash of the script is the merkle root)
control_block = control_byte + internal_pubkey
#=> c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

# -------------------
# Signature Algorithm
# -------------------

# unsigned raw tx:
#
# 020000000001013cfe8b95d22502698fd98837f83d8d4be31ee3eddd9d1ab1a95654c64604c4d10000000000ffffffff01983a0000000000001600140de745dc58d8e62e6f47bde30cd5804a82016f9e0000000000

hash_type = 1 # SIGHASH_ALL
hash_type_byte = field(dechex(hash_type), 1)

ext_flag = 1 # Use ext_flag = 1 to indicate we are spending using tapscript (i.e. a script path spend of a taproot output)
annex_present = 0 # 1 = true, 0 = false
spend_type = field(dechex((ext_flag * 2) + annex_present), 1)

version = '02000000'
locktime = '00000000'

# hash_type & 0x80 != SIGHASH_ANYONECANPAY
prevouts = '3cfe8b95d22502698fd98837f83d8d4be31ee3eddd9d1ab1a95654c64604c4d1'+'00000000'
amounts = reversebytes(field(dechex('20000'), 8))
sequences = 'ffffffff'
scriptpubkeys = serialize_script('5120f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80')

sha_prevouts = Digest::SHA256.hexdigest([prevouts].pack("H*"));           #=> fd9703aeae8f25e8734366f3be9b5e7ac2a56772d577c598cfa6e869c698f7eb
sha_amounts = Digest::SHA256.hexdigest([amounts].pack("H*"));             #=> ae9475d31b535bec000c9bfc7abc79b6a07db9eea2dd0e5066adddfb349bb53b
sha_sequences = Digest::SHA256.hexdigest([sequences].pack("H*"));         #=> ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e
sha_scriptpubkeys = Digest::SHA256.hexdigest([scriptpubkeys].pack("H*")); #=> ef0ffac40687f5fc2a58c5348cd6f17469c333d0f2782769e0c3f05e97062182

outputs = '983a000000000000' + serialize_script('00140de745dc58d8e62e6f47bde30cd5804a82016f9e')
sha_outputs = Digest::SHA256.hexdigest([outputs].pack("H*")); #=> 41c602530c3cfa80923771e080db8674c730480f9764290bc5b372ae28cf8dbc

input_outpoint = '3cfe8b95d22502698fd98837f83d8d4be31ee3eddd9d1ab1a95654c64604c4d1'+'00000000'
input_amount = reversebytes(field(dechex('20000'), 8))
input_scriptpubkey = serialize_script('5120f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80') # always 32 bytes
input_sequence = 'ffffffff'

input_index = '00000000' # 4-byte vin of the input you're spending

sha_annex = '' # SHA256 of (compact_size(size of annex) || annex), where annex includes the mandatory 0x50 prefix

sha_single_output = ''


# common signature message
sigmsg = ''
sigmsg += hash_type_byte + version + locktime

# NOT SIGHASH_ANYONECANPAY (0x80)
if (hash_type & 0x80 == 0)
  sigmsg += sha_prevouts + sha_amounts + sha_scriptpubkeys + sha_sequences
end

# NOT SIGHASH_NONE (0x02/0x82) OR SIGHASH_SINGLE (0x03/0x83)
if (hash_type & 3 < 2)
  sigmsg += sha_outputs
end

sigmsg += spend_type

# NOT SIGHASH_ANYONECANPAY (0x80)
if (hash_type & 0x80 == 0)
  sigmsg += input_index

# SIGHASH_ANYONECANPAY (0x80)
else
  sigmsg += input_outpoint + input_amount + input_scriptpubkey + input_sequence
end

if (annex_present == 1)
  annex = ''
  sha_annex = Digest::SHA256.hexdigest([annex].pack("H*"))
  sigmsg += sha_annex
end

# SIGHASH_SINGLE
if (hash_type & 3 == 3)
  sigmsg += sha_single_output
end

# SigMsg extension (BIP 342)
if (ext_flag == 1)

  # <extension> = <tap leaf hash> <key version = 0x00> <codesep position = 0xffffffff>
  #   <tap leaf hash>    = the leaf hash of the script you're spending
  #   <key version>      = public key version (currently 0x00)
  #   <codesep position> = the opcode position of the last OP_CODESEPARATOR (0xffffffff if none)
  extension = leaf_hash + '00' + 'ffffffff'

  sigmsg += extension
#=> 010200000000000000fd9703aeae8f25e8734366f3be9b5e7ac2a56772d577c598cfa6e869c698f7ebae9475d31b535bec000c9bfc7abc79b6a07db9eea2dd0e5066adddfb349bb53bef0ffac40687f5fc2a58c5348cd6f17469c333d0f2782769e0c3f05e97062182ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e41c602530c3cfa80923771e080db8674c730480f9764290bc5b372ae28cf8dbc0200000000858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef1600ffffffff
end

# sighash
sighash = tagged_hash("TapSighash", '00' + sigmsg) # don't forget the 0x00 prefix to the sigmsg
#=> 752453d473e511a0da2097d664d69fe5eb89d8d9d00eab924b42fc0801a980c9

# ---------
# Signature
# ---------

# signing data
private_key = '9b8de5d7f20a8ebb026a82babac3aa47a008debbfde5348962b2c46520bd5189'
message = sighash
aux_rand = '0000000000000000000000000000000000000000000000000000000000000000' # all signatures are created with an all-zero (0x0000...0000) BIP340 auxiliary randomness array.

# ----
# Sign
# ----

# convert private key to an integer
d0 = int(private_key)

# make sure private key is in valid range (greater than 0 and less than the number of points on the curve)
unless (1..$n-1).include?(d0)
  raise "private key must be in the range 1..n-1"
end

# calculate the public key point from the private key
public_key_point = multiply(d0) # multiply() checks for point at infinity

# negate the private key if the public key it creates doesn't have an even y value, else keep the private key the same
# note: due to the way the elliptic curve works, negate the private key will produce a public key with the same x coordinate, but the opposite y value
if public_key_point[:y] % 2 != 0
  d = $n - d0
else
  d = d0
end

# create a tagged hash of the auxiliary bytes
aux_rand_hash = tagged_hash("BIP0340/aux", aux_rand)

# first step toward creating the nonce is to XOR the private key with the hash of the auxiliary bytes
t = d ^ int(aux_rand_hash)

# create the nonce by hashing t (from the previous step) along with the public_key and message
k0 = int(tagged_hash("BIP0340/nonce", bytes(t) + bytes(public_key_point[:x]) + message)) % $n # public key is included in hash for "key-prefixed" schnorr signatures

# check that the nonce isn't zero
if k0 == 0
  raise "nonce must not be zero (this is almost impossible, but checking anyway)"
end

# use this nonce to get a point on the curve
random_point = multiply(k0) # multiply() checks for point at infinity

# negate the nonce used to create the random point if the public key it creates doesn't have an even y value
if random_point[:y] % 2 != 0
  k = $n - k0
  # note: due to the way the elliptic curve works, the inverse private key will produce an even y value
else
  k = k0
end

# create the challenge e value by hashing the random point with the public key and message
e = int(tagged_hash("BIP0340/challenge", bytes(random_point[:x]) + bytes(public_key_point[:x]) + message)) % $n

# r value is the x-coordinate of point R
r =  random_point[:x]

# s value: (k + e*d) mod n
s = (k + e * d) % $n # this is linear (whereas s in ECDSA is non-linear)

# signature is the r and s values converted to 32-byte hexadecimal string and concatenated
sig = bytes(r) + bytes(s)
#=> 01769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f

# witness (signature + hash_type byte)
signature = sig + hash_type_byte
#=> 01769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f01

# NOTE: If the hash_type_byte is not given (i.e. the signature is 64 instead of 64 bytes), the hash_type is assumed to be 0x00
# Why permit two signature lengths? By making the most common type of hash_type implicit, a byte can often be saved.

# -------
# Witness
# -------
# <script inputs> <script> <control block>

script_inputs = signature # CAUTION: The witness only contains data pushes, unlike the scriptSig.
script        = '206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac' # OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
control_block = control_block

witness = 
  compact_size(3) + 
  compact_size(script_inputs.length / 2) + script_inputs + 
  compact_size(script.length / 2) + script + 
  compact_size(control_block.length / 2) + control_block

puts witness
#=> 034101769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f0122206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac21c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

4. Script Path Spend (Tree)

This is another example of a script path spend, but this time using a complex script tree.

This example highlights how to construct a complex script tree, as well as the resulting merkle path required when spending to prove your chosen leaf script was part of the original tree.

For the purposes of this example, the leaf scripts are extremely basic and do not require a signature (e.g. OP_N OP_EQUAL).

These leaf scripts are not secure. If you use these kind of scripts in the real world your coins are likely to be stolen when you broadcast the spending transaction (which is actually what happened to me when I spent this output).

Construct

public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

┌────────┐ ┌────────┐                
│ leaf 1 │ │ leaf 2 │                
└───┬────┘ └───┬────┘                
    └────┬─────┘                     
     ┌───┴────┐ ┌────────┐         
     │branch 1│ │ leaf 3 │         
     └───┬────┘ └─────┬──┘           
         └─────┬──────┘              
           ┌───┴────┐ ┌────────┐     
           │branch 2│ │ leaf 4 │     
           └───┬────┘ └────┬───┘     
               └────┬──────┘         
                ┌───┴────┐ ┌────────┐
                │branch 3│ │ leaf 5 │
                └───┬────┘ └────┬───┘
                    └─────┬─────┘    
                      ┌───┴────┐     
                      │branch 4│     
                      └────────┘

leaf 1 version      = c0
size(leaf 1 script) = 02
leaf 1 script       = 5187
leaf 1 hash         = 6b13becdaf0eee497e2f304adcfa1c0c9e84561c9989b7f2b5fc39f5f90a60f6

leaf 2 version      = c0
size(leaf 2 script) = 02
leaf 2 script       = 5287
leaf 2 hash         = ed5af8352e2a54cce8d3ea326beb7907efa850bdfe3711cef9060c7bb5bcf59e

leaf 3 version      = c0
size(leaf 3 script) = 02
leaf 3 script       = 5387
leaf 3 hash         = 160bd30406f8d5333be044e6d2d14624470495da8a3f91242ce338599b233931

leaf 4 version      = c0
size(leaf 4 script) = 02
leaf 4 script       = 5487
leaf 4 hash         = bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d

leaf 5 version      = c0
size(leaf 5 script) = 02
leaf 5 script       = 5587
leaf 5 hash         = 54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

branch 1 (leaf 1 + leaf 2)   = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9
branch 2 (branch 1 + leaf 3) = beec0122bddd26f642140bcd922e0264ce1e2be5808a41ae58d82e829bc913d7
branch 3 (branch 2 + leaf 4) = a4e0d9cc12ce2f32069e98247581d5eb9ca0a4cf175771a8df2c53a93dcb0ebd
branch 4 (leaf 5 + branch 3) = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

merkle root         = b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c

tweak               = 28dcaf275e25b339c2b8362dd0db3347fc7336602b2b52d95ffae0149038776c
tweaked public key  = 979cff99636da1b0e49f8711514c642f640d1f64340c3784942296368fadd0a5

scriptpubkey        = 5120979cff99636da1b0e49f8711514c642f640d1f64340c3784942296368fadd0a5

TXID: ec7b0fdfeb2c115b5a4b172a3a1cf406acc2425229c540d40ec752d893aac0d7 (Output 0)

This example uses an unbalanced tree. You'd use this kind of structure when leaf 5 is more likely to be executed than the other leaves. In this example I'll actually be spending this output using leaf 3.

Don't forget that the lower leaf/branch hash goes first when combining previous leaf/branch hashes to construct the next branch hash. This applies to branch hash 4 in this example (i.e. leaf hash 5 goes before branch hash 3).

The merkle root is the final hash in the tree, which is branch 4.

Spend

script inputs       = 03
script              = 5387

control byte        = c0
public key          = 924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329

┌────────┐ ┌────────┐                
│ leaf 1 │ │ leaf 2 │                
└───┬────┘ └───┬────┘                
    └────┬─────┘                     
     X───┴────┐ ┌────────┐         
     │branch 1│ │ leaf 3 │ <- spending           
     └───┬────┘ └─────┬──┘           
         └─────┬──────┘              
           ┌───┴────┐ X────────┐     
           │branch 2│ │ leaf 4 │     
           └───┬────┘ └────┬───┘     
               └────┬──────┘         
                ┌───┴────┐ X────────┐
                │branch 3│ │ leaf 5 │
                └───┬────┘ └────┬───┘
                    └─────┬─────┘    
                      ┌───┴────┐     
                      │branch 4│     
                      └────────┘   Note: X = hashes required to construct the merkle path

merkle path         = 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f
control block       = c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a33291324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

witness             = 03010302538781c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a33291324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

TXID: 992af7eb67f37a4dfaa64ea6f03a70c35b6063ba5ee3fe41734c3460b4006463 (Input 0)

This P2TR is being unlocked using leaf 3.

The script input is 03, which satisfies the original leaf script of OP_3 OP_EQUAL.

The merkle path is branch hash 1 + leaf hash 4 + leaf hash 5. These are the hashes required (in order) to be able to reconstruct the merkle root during validation (which proves that leaf 3 was part of the original script tree). As you can see, none of the raw scripts are actually revealed inside the control block.

The resulting witness is the witness encoding of the following items: script inputs + leaf script + control block.

Code

This code requires examples-taproot-common.rb

require_relative 'examples-taproot-common.rb' # common functions
require "digest" # library for SHA256 hash function

# =========
# Construct
# =========

# ----------
# Public Key
# ----------

internal_pubkey = '924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329'

# -----------
# Merkle Tree
# -----------
#                                
# ┌────────┐ ┌────────┐                
# │ leaf 1 │ │ leaf 2 │                
# └───┬────┘ └───┬────┘                
#     └────┬─────┘                     
#      X───┴────┐ ┌────────┐         
#      │branch 1│ │ leaf 3 │ <- spending           
#      └───┬────┘ └─────┬──┘           
#          └─────┬──────┘              
#            ┌───┴────┐ X────────┐     
#            │branch 2│ │ leaf 4 │     
#            └───┬────┘ └────┬───┘     
#                └────┬──────┘         
#                 ┌───┴────┐ X────────┐
#                 │branch 3│ │ leaf 5 │
#                 └───┬────┘ └────┬───┘
#                     └─────┬─────┘    
#                       ┌───┴────┐     
#                       │branch 4│     
#                       └────────┘     
#
# X = hashes required to construct the merkle path 

# leaf 1
leaf_1_version = 192
leaf_1_script = '5187' # OP_1 OP_EQUAL
leaf_1_hash = tagged_hash("TapLeaf", field(dechex(leaf_1_version), 1) + serialize_script(leaf_1_script))
#=> 6b13becdaf0eee497e2f304adcfa1c0c9e84561c9989b7f2b5fc39f5f90a60f6

# leaf 2
leaf_2_version = 192
leaf_2_script = '5287' # OP_2 OP_EQUAL
leaf_2_hash = tagged_hash("TapLeaf", field(dechex(leaf_2_version), 1) + serialize_script(leaf_2_script))
#=> ed5af8352e2a54cce8d3ea326beb7907efa850bdfe3711cef9060c7bb5bcf59e

# leaf 3
leaf_3_version = 192
leaf_3_script = '5387' # OP_3 OP_EQUAL
leaf_3_hash = tagged_hash("TapLeaf", field(dechex(leaf_3_version), 1) + serialize_script(leaf_3_script))
#=> 160bd30406f8d5333be044e6d2d14624470495da8a3f91242ce338599b233931

# leaf 4
leaf_4_version = 192
leaf_4_script = '5487' # OP_4 OP_EQUAL
leaf_4_hash = tagged_hash("TapLeaf", field(dechex(leaf_4_version), 1) + serialize_script(leaf_4_script))
#=> bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d

# leaf 5
leaf_5_version = 192
leaf_5_script = '5587' # OP_4 OP_EQUAL
leaf_5_hash = tagged_hash("TapLeaf", field(dechex(leaf_5_version), 1) + serialize_script(leaf_5_script))
#=> 54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f


# branch 1: (leaf 1 + leaf 2)
branch_1_hash = tagged_hash("TapBranch", leaf_1_hash + leaf_2_hash)
#=> 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9

# branch 2: (branch 1 + leaf 3)
branch_2_hash = tagged_hash("TapBranch", branch_1_hash + leaf_3_hash)
#=> beec0122bddd26f642140bcd922e0264ce1e2be5808a41ae58d82e829bc913d7

# branch 3: (branch 2 + leaf 4)
branch_3_hash = tagged_hash("TapBranch", branch_2_hash + leaf_4_hash)
#=> a4e0d9cc12ce2f32069e98247581d5eb9ca0a4cf175771a8df2c53a93dcb0ebd

# branch 4: (branch 3 + leaf 5)
branch_4_hash = tagged_hash("TapBranch", leaf_5_hash + branch_3_hash) # lower hash comes first
#=> b5b72eea07b3e338962944a752a98772bbe1f1b6550e6fb6ab8c6e6adb152e7c


# merkle root
merkle_root = branch_4_hash # this branch is the merkle root

# ----------------
# Tweak Public Key
# ----------------

# create a hash from the public key and merkle root - this will be used to tweak the public key
tweak = tagged_hash("TapTweak", internal_pubkey + merkle_root)
#=> 28dcaf275e25b339c2b8362dd0db3347fc7336602b2b52d95ffae0149038776c

# tweak the public key
t = int(tweak) # convert to integer (so it's like a private key) (check this is less than curve order)
internal_pubkey_point = lift_x(internal_pubkey)
tweaked_pubkey_point = add(internal_pubkey_point, multiply(t, $G)) # (check y is even)
tweaked_pubkey = bytes(tweaked_pubkey_point[:x])
#=> 979cff99636da1b0e49f8711514c642f640d1f64340c3784942296368fadd0a5

# convert to scriptpubkey
scriptpubkey = '51' + '20' + tweaked_pubkey
#=> 5120979cff99636da1b0e49f8711514c642f640d1f64340c3784942296368fadd0a5


# =====
# Spend
# =====

# unsigned raw tx:
#
# 02000000000101d7c0aa93d852c70ed440c5295242c2ac06f41c3a2a174b5a5b112cebdf0f7bec0000000000ffffffff014c1d0000000000001600140de745dc58d8e62e6f47bde30cd5804a82016f9e0000000000

# -------------
# Control Block
# -------------
# <control byte with leaf version and parity bit> <internal pubkey> <leaves/branches required to calculate merkle root>

# note: all leaf_versions are 192
control_byte = calculate_control_byte(192, tweaked_pubkey_point)

# merkle path - spending leaf 3 script (OP_3 OP_EQUAL), so need to provide the merkle path to be able to calculate the merkle root
merkle_path = branch_1_hash + leaf_4_hash + leaf_5_hash
#=> 1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

# control block
control_block = control_byte + internal_pubkey + merkle_path
#=> c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a33291324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

# -------
# Witness
# -------
# <script inputs> <script> <control block>

script_inputs = '03' # CAUTION: The witness only contains data pushes, unlike the scriptSig.
script        = leaf_3_script # 5387 (OP_3 OP_EQUAL)
control_block = control_block # c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a33291324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d54962df196af2827a86f4bde3cf7d7c1a9dcb6e17f660badefbc892309bb145f

witness = 
  compact_size(3) + 
  compact_size(script_inputs.length / 2) + script_inputs + 
  compact_size(script.length / 2) + script + 
  compact_size(control_block.length / 2) + control_block

puts witness
#=> 03010302538761c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329bf2c4bf1ca72f7b8538e9df9bdfd3ba4c305ad11587f12bbfafa00d58ad6051d1324300a84045033ec539f60c70d582c48b9acf04150da091694d83171b44ec9

History

When did the Taproot upgrade take place?

The idea behind Taproot was first proposed by Gregory Maxwell in 2018.

The BIPs outlining the specific technical changes to Bitcoin were organized by Pieter Wuille and released in 2020:

The Taproot upgrade was eventually activated on Bitcoin on .

Deployment

The deployment of the Taproot upgrade used version bits signalling.

For the upgrade to be accepted, 90% of miners had to signal for the upgrade over a 2016-block target adjustment period during the following window:

Start:
End:

If 90% of miners signalled readiness for the Taproot upgrade during any of the target adjustment periods, the upgrade would be activated at the minimum activation height of 709,632.

Signalling Results

Start Time Target Period Signalling Blocks Percentage Note
679,392 to 681,407 0/2016 0.00%
681,408 to 683,423 823/2016 40.82% First period in signalling window.
683,424 to 685,439 1690/2016 83.83%
685,440 to 687,455 1983/2016 98.36% Successful signalling period
687,456 to 689,471 2011/2016 99.75%
689,472 to 691,487 2010/2016 99.70%
691,488 to 693,503 2013/2016 99.85% Last period in signalling window
693,504 to 695,519 1990/2016 98.71%
695,520 to 697,535 1957/2016 97.07%

This 90% threshold was exceeded for the first time on , with 98.36% (1983/2016) of blocks signalling for the Taproot upgrade.

The Taproot upgrade was then activated at block height 709,632 on .

Etymology

Why is it called "Taproot"?

A taproot is a large, central, and dominant root from which other roots sprout laterally.
Wikipedia

The name "Taproot" was coined by Gregory Maxwell:

The name originated in a visualization of a tree with a thick central truck like a dandelion taproot -- the technique is mostly useful because of the assumption that there is one high probability path and the rest is fuzzy stragglers, and I thought it was a good one because of the punny fact that it verifies script-path spends by tapping into the hidden commitment in the root.
Gregory Maxwell, Bitcoin Optech Github (Pull Request 667)

The idea is that the primary spending condition (key path spend) is the taproot, and all of the other possible spending conditions (script path spends) are the smaller roots branching off from the main root.

The name also stems from the fact that all the additional spending conditions converge in to a single merkle root.

Diagram showing an illustration of a dandelion root showing how the main taproot corresponds to a key path spend and the smaller roots corresponding to script path spends.
Dandelion illustration by Janine Wiget

Summary

The Taproot upgrade basically introduced the "ultimate" locking mechanism for bitcoins.

This new P2TR locking script provides all of the functionality of all previous locking scripts in one single locking script. In other words; if you could only have one locking script in Bitcoin, it would be P2TR.

From a technical point of view, P2TR simply combines a standard public key lock with the option of including multiple custom spending conditions alongside it. It's like a P2WPKH and P2WSH rolled in to one, with Schnorr signatures and tapscript thrown in for good measure.

It's very useful, and pretty clever.

Understanding how P2TR works is much easier if you're already familiar with merkle trees and elliptic curve cryptography. But even if you're not, implementing it in to existing code isn't too difficult; the trickiest part is not forgetting all the annoying additional bytes that need to be included along the way (e.g. the pesky sighash epoch).

But once you've got it working, P2TR is an extremely handy locking script to have in your toolbox.

Resources