§ 06

§ 06 · Script

P2TR

One key in front, any number of scripts behind it

Pay-to-Taproot is the output nearly every Pearl coin sits in. It looks like a single public key, and most of the time it is used like one. But that key can also carry a hidden list of scripts, any one of which could spend the coin instead.

Two ways in, then: the key path, one signature, and the script path, a script plus proof that it was part of the deal from the start. From the outside, you can’t tell whether a Taproot output has scripts at all until one is used.

Try it

Turn a private key into a Taproot output key and address.

Key forge

Private key → public key → Taproot tweak → prl1 address

For learning only. A key shown on a web page is not secret. Never send real PRL to an address made here.

Key 1 is the most famous key there is: its public key is the generator point G itself. Anyone can spend from its addresses.

  1. 1

    Private key d

    A secret number between 1 and n − 1 (n ≈ 1.16 × 10⁷⁷). 32 bytes.

    0000000000000000000000000000000000000000000000000000000000000001

  2. 2

    Public key P = d·G

    Multiply the curve’s starting point G by d. Compressed: a parity byte, then x.

    0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

    02 · y is even Taproot keeps only x (32 bytes, “x-only”) and assumes the even y.

  3. 3

    Tweak t

    t = TaggedHash(“TapTweak”, P.x)

    3cf5216d476a5e637bf0da674e50ddf55c403270dd36494dfcca438132fa30e7

    BIP-86: no script tree, so the tweak commits to the key alone. It proves there is no hidden script.

  4. 4

    Output key Q = P + t·G

    The key that actually goes on the chain. Its private key is d + t (with d negated first if P’s y was odd).

    da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21

  5. 5

    Address bech32m

    Prefix + “1” + witness version 1 (“p”) + Q in 5-bit letters + a 6-letter checksum.

    prl1pmfr3p9j00pfxjh0zmgp99y8zftmd3s5pmedqhyptwy6lm87hf5sse9xfq3

    scriptPubKey 51 20 da 47 10 96… (63 characters)

Runs in your browser · nothing is sent · keys are never saved src/lib/keys.ts · bech32m.ts

What does a Taproot output look like?

Size
34 bytes
Type
Witness version 1 program
Format
51 20 ‖ 32-byte output key Q

51 is OP_1 (witness version 1), 20 pushes 32 bytes, and those bytes are the x-only output key Q. Here is one from a real payment:

A typical payment: 1 input, 2 outputs 205 bytes
01000000 00 01 01 5939557091bae792be66f26cbd83fc78f16a690a854109e8a1d115e253812133 ab020000 00 ffffffff 02 b0359f1100000000 22 5120d7ab02c96a342bdddb5d07b9f3ebd6c04e077ff41418110b9a1e5997439416b2 00c2eb0b00000000 22 512062e9da3682aab288392cf7218bafc36287288abb3ce200c5d1a50aa234073b2b 01 40 4686c7014749649b25555874c7772ecb7a44e85454893ed5888db11ebcdc82a22d74ce1a1671d02810f5ba1dca3277409e95a653ba9644099f7da26be7a86ba2 00000000
Field Bytes Value
Version version 4 @0 1 int32 little-endian. Pearl wallets and pearld coinbases use version 1; relay policy accepts 1-2.
Marker marker 1 @4 0x00 Segwit marker (BIP-144): says witness data follows the outputs.
Flag flag 1 @5 0x01 Witness flag; 0x01 is the only value Pearl accepts.
Input count vin.length 1 @6 1 CompactSize number of inputs.
Previous txid vin[0].txid 32 @7 33218153e215d1a1e80941850a696af178fc83bd6cf266be92e7ba9170553959 The transaction being spent, stored byte-reversed (internal order); shown here in normal display order.
Output index vin[0].vout 4 @39 683 Which output of that transaction is spent (uint32 LE).
scriptSig length vin[0].scriptSig.length 1 @43 0 Always 0 for non-coinbase inputs in Pearl: every spend uses the witness instead.
Sequence vin[0].sequence 4 @44 0xffffffff Final (0xffffffff): no RBF signal, no relative locktime, and locktime is ignored for this input. Pearl wallets use this.
Output count vout.length 1 @48 2 CompactSize number of outputs.
Amount vout[0].value 8 @49 295,646,640 grains (2.95646640 PRL) int64 little-endian, in grains (1 PRL = 100,000,000 grains).
Script length vout[0].scriptPubKey.length 1 @57 34 CompactSize length of the locking script.
Locking script vout[0].scriptPubKey 34 @58 prl1p674s9jt2xs4amk6aq7ul867kcp8qwll5zsvpzzu6revewsu5z6eqv9d5z4 OP_1 OP_PUSHBYTES_32 <output key Q>: a Taproot output.
Amount vout[1].value 8 @92 200,000,000 grains (2.00000000 PRL) int64 little-endian, in grains (1 PRL = 100,000,000 grains).
Script length vout[1].scriptPubKey.length 1 @100 34 CompactSize length of the locking script.
Locking script vout[1].scriptPubKey 34 @101 prl1pvt5a5d5z42egswfv7uscht7rv2rj3z4m8n3qp3w35592ydq88v4seuhn70 OP_1 OP_PUSHBYTES_32 <output key Q>: a Taproot output.
Witness items witness[0].length 1 @135 1 Number of witness stack items for input 0.
Schnorr signature length witness[0][0].length 1 @136 64 CompactSize length of this witness item.
Schnorr signature witness[0][0] 64 @137 4686c7014749649b25555874c7772ecb7a44e85454893ed5888db11ebcdc82a22d74ce1a1671d02810f5ba1dca3277409e95a653ba9644099f7da26be7a86ba2 64-byte BIP-340 signature (R.x ‖ s) with SIGHASH_DEFAULT: a Taproot key-path spend.
Locktime locktime 4 @201 0 0: no time lock.
txid
d919e02a4adfd739ceef31d98803f0ddd24e1b1fa67b0fe572cdec42c1b384c9
Size
205 bytes · 154 vbytes
Block
130,825
Fee
7,915 grains

Written as an address, that’s prl1pvt5a5d5z42egswfv7uscht7rv2rj3z4m8n3qp3w35592ydq88v4seuhn70. Q was made by tweaking an internal key P with the root of a script tree; for a plain wallet address, the tree is empty.

output key Qkey pathone signatureP + TapTweak(P, root)root of the treeleaf Aleaf Bleaf Cwhat wallets doscript path:
A Taproot output commits to two ways of spending. The key path needs only a signature for Q; the script path reveals one leaf and proves it is in the tree.

How does the key path work?

The witness holds exactly one item, a Schnorr signature, and the node checks it against Q and the transaction’s sighash:

A typical payment: 1 input, 2 outputs 205 bytes
01000000 00 01 01 5939557091bae792be66f26cbd83fc78f16a690a854109e8a1d115e253812133 ab020000 00 ffffffff 02 b0359f1100000000 22 5120d7ab02c96a342bdddb5d07b9f3ebd6c04e077ff41418110b9a1e5997439416b2 00c2eb0b00000000 22 512062e9da3682aab288392cf7218bafc36287288abb3ce200c5d1a50aa234073b2b 01 40 4686c7014749649b25555874c7772ecb7a44e85454893ed5888db11ebcdc82a22d74ce1a1671d02810f5ba1dca3277409e95a653ba9644099f7da26be7a86ba2 00000000
Field Bytes Value
Version version 4 @0 1 int32 little-endian. Pearl wallets and pearld coinbases use version 1; relay policy accepts 1-2.
Marker marker 1 @4 0x00 Segwit marker (BIP-144): says witness data follows the outputs.
Flag flag 1 @5 0x01 Witness flag; 0x01 is the only value Pearl accepts.
Input count vin.length 1 @6 1 CompactSize number of inputs.
Previous txid vin[0].txid 32 @7 33218153e215d1a1e80941850a696af178fc83bd6cf266be92e7ba9170553959 The transaction being spent, stored byte-reversed (internal order); shown here in normal display order.
Output index vin[0].vout 4 @39 683 Which output of that transaction is spent (uint32 LE).
scriptSig length vin[0].scriptSig.length 1 @43 0 Always 0 for non-coinbase inputs in Pearl: every spend uses the witness instead.
Sequence vin[0].sequence 4 @44 0xffffffff Final (0xffffffff): no RBF signal, no relative locktime, and locktime is ignored for this input. Pearl wallets use this.
Output count vout.length 1 @48 2 CompactSize number of outputs.
Amount vout[0].value 8 @49 295,646,640 grains (2.95646640 PRL) int64 little-endian, in grains (1 PRL = 100,000,000 grains).
Script length vout[0].scriptPubKey.length 1 @57 34 CompactSize length of the locking script.
Locking script vout[0].scriptPubKey 34 @58 prl1p674s9jt2xs4amk6aq7ul867kcp8qwll5zsvpzzu6revewsu5z6eqv9d5z4 OP_1 OP_PUSHBYTES_32 <output key Q>: a Taproot output.
Amount vout[1].value 8 @92 200,000,000 grains (2.00000000 PRL) int64 little-endian, in grains (1 PRL = 100,000,000 grains).
Script length vout[1].scriptPubKey.length 1 @100 34 CompactSize length of the locking script.
Locking script vout[1].scriptPubKey 34 @101 prl1pvt5a5d5z42egswfv7uscht7rv2rj3z4m8n3qp3w35592ydq88v4seuhn70 OP_1 OP_PUSHBYTES_32 <output key Q>: a Taproot output.
Witness items witness[0].length 1 @135 1 Number of witness stack items for input 0.
Schnorr signature length witness[0][0].length 1 @136 64 CompactSize length of this witness item.
Schnorr signature witness[0][0] 64 @137 4686c7014749649b25555874c7772ecb7a44e85454893ed5888db11ebcdc82a22d74ce1a1671d02810f5ba1dca3277409e95a653ba9644099f7da26be7a86ba2 64-byte BIP-340 signature (R.x ‖ s) with SIGHASH_DEFAULT: a Taproot key-path spend.
Locktime locktime 4 @201 0 0: no time lock.
txid
d919e02a4adfd739ceef31d98803f0ddd24e1b1fa67b0fe572cdec42c1b384c9
Size
205 bytes · 154 vbytes
Block
130,825
Fee
7,915 grains

64 bytes means the default sighash; 65 bytes means the last byte is an explicit type. The signer uses the tweaked private key d + t, which wallets derive from the internal key whenever they sign. Nothing about scripts is revealed, or even whether there are any. This is a key-path spend, and it’s what Pearl’s wallet does for every coin.

How does the script path work?

The witness holds the script’s inputs, then the script, then a control block:

A Taproot control block
Field Size Description
leaf version | parity 1 c0 (tapscript) OR the parity of Q's y coordinate (0 or 1): c0 or c1.
internal key P 32 The x-only key that was tweaked to make Q.
Merkle path 32 × m The sibling hashes from the leaf up to the root; m is the leaf's depth (0 to 128).

Then the node checks the commitment, in four steps:

  1. Hash the leaf

    TapLeaf hash = TaggedHash(“TapLeaf”, c0 ‖ script length ‖ script).
  2. Climb the tree

    Combine it with each path hash in turn: TaggedHash(“TapBranch”, smaller ‖ larger). The last result is the root.
  3. Redo the tweak

    t = TaggedHash(“TapTweak”, P ‖ root), and Q’ = P + t·G.
  4. Compare

    Q’ must equal the output’s Q, and its y parity must match the control block’s low bit. Then the script runs as tapscript, with the witness items before it as its starting stack.

A worked example

This test vector was produced with Pearl’s own code by the research for this site. Its previous outpoint is made up; everything else is real Pearl behavior, and Pearl’s engine accepted it.

Value
Internal key P 79be667e…16f81798 (the generator, private key 1)
The one leaf 20 e493dbf1…c4cd13 ac: push the key of private key 4, then OP_CHECKSIG
Leaf hash = root 0e167d3d619d2b16fb08736a6e696eb6814c6d99a578c8c268d4195aeb9f72d1
Output key Q 3bb7c61536fc47c697ed6d21042fc79a6f491ef982f605ba7ca277bbd79aa25c (even y)
Address prl1p8wmuv9fkl3rud9ldd5ssgt78nfh5j8hestmqtwnu5fmmh4u65fwqm3tz0r
Script-path witness (test vector) 135 bytes
03 40 a4fdc52f6cf77234304c7452440ca89c3d230df22a478134e13c2b52d89c01b07d9073af64632775a3bd6b39374e768523515829759ae0c055dc196c11bd6e33 22 20e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13ac 21 c079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Field Bytes Value
item count 1 @0 3 items
length 1 @1 64
signature (key 4) 64 @2 a4fdc52f6cf77234304c7452440ca89c3d230df22a478134e13c2b52d89c01b07d9073af64632775a3bd6b39374e768523515829759ae0c055dc196c11bd6e33
length 1 @66 34
leaf script 34 @67 <key 4> OP_CHECKSIG
length 1 @101 33
control block 33 @102 c0 (parity 0) ‖ P; no path
From the research's Pearl-code test vector 15.G. Made-up outpoint; the 64-byte signature is by private key 4.

The leaf starts with the signature on its stack, pushes key 4, and OP_CHECKSIG checks the signature: true, success. Mainnet has script-path Taproot spends too; one in block 130,690 used a 33-byte control block like this one.

Why do wallets prefer the key path?

Because it’s smaller and it says less.

Spend Witness bytes Witness vbytes Reveals
Key path (the payment above) 66 16.5 one signature
Script path (the test vector above) 135 33.75 the script, the internal key, the tree position

The witness counts a quarter, so the difference per input is small in fees. The bigger difference is privacy: a key-path spend looks like every other key-path spend on the chain, whether or not the output had scripts behind it. A script-path spend shows the world one of the conditions, and that a tree existed.

So the usual design is: put the common case (everyone agrees) on the key path, and keep scripts for the exceptions. Pearl’s quantum-safe addresses follow it: the XMSS leaf is there for an emergency, and everyday spends use the key.

What do Pearl wallets do with Taproot?

  • Default addresses are key-path only. Oyster derives a BIP-86 key and tweaks it with an empty tree. There is no script to reveal; every spend is a single signature.
  • Quantum-safe addresses add one leaf. getnewaddress … pq=true commits a leaf that checks an XMSS signature (OP_CHECKXMSSSIG). The key path still works, and a search of the v1.5.2 code found no production XMSS signer, so the wallet spends these by key path too. The leaf is meant as an emergency exit, but while the key path stays open, anyone who could break Schnorr could simply use that instead.

What rules apply?

  • The 32 bytes are not checked to be a valid point when the output is created, as in Bitcoin. A wrong one just makes the coin unspendable.
  • Leaf version must be 0xc0. Bitcoin lets unknown leaf versions pass, for future upgrades; Pearl fails them (“unsupported tapscript leaf version”).
  • Control blocks are 33 + 32m bytes, m at most 128, so at most 4,129 bytes.
  • An annex may follow the control block (see witness).

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.