§ 06 · Script
P2TR
One key in front, any number of scripts behind it
On this page 7 sections
Pay-to-TaprootP2TR (Pay-to-Taproot): The standard Pearl output: witness version 1, with addresses starting prl1p. It can be spent with one Schnorr signature, or through a hidden tree of scripts. 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.
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:
| 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. |
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.
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:
| 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. |
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 spendKey-path spend: Spending a Taproot output with one Schnorr signature from the tweaked key, and nothing else in the witness. It is how Pearl wallets spend almost every coin. P2MR outputs have no key path., 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 blockControl block: The last item of a script-path witness: a few bytes proving that the script being run really belongs to the output's script tree.:
| 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:
-
Hash the leaf
TapLeaf hash = TaggedHash(“TapLeaf”,c0‖ script length ‖ script). -
Climb the tree
Combine it with each path hash in turn: TaggedHash(“TapBranch”, smaller ‖ larger). The last result is the root. -
Redo the tweak
t = TaggedHash(“TapTweak”, P ‖ root), and Q’ = P + t·G. -
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 |
| 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 |
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=truecommits 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).