Taproot
Taproot outputs can be spent in multiple ways. Either using an "internal key" or by satisfying one of the output scripts present as a leaf in the taproot output’s merkle tree. Further, these outputs need to be signed using Schnorr signatures. These possibilities make it challenging for developers to describe a taproot output and then to provide signatures for it.
Bitcoin descriptors use strings to describe the output conditions. For example the descriptors page page specifies a taproot output with two potential script paths as follows:
tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,
{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),
pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})
Such a format is handy when you need to copy-paste output specifications across hardware wallets etc. However, such output descriptions will be a pain point for developers, who prefer to work with first class data structures like arrays and dictionaries.
The DSL captures taproot outputs (and signature specifications) as an object notation with support for arrays to describe output leaves. Each of the script leaves can be bitcoin descriptors or normal bitcoin scripts. The DSL allows developers to explicitly state the keypath and the script leaves.
As an example, the transaction below spends to a taproot output that allows spending using a tweaked internal key or any of the leaves.
@taproot_output_tx = transaction inputs: [
{ tx: @coinbase_tx,
vout: 0,
script_sig: 'sig:wpkh(@alice)' }
],
outputs: [
{
taproot: { internal_key: @bob, (1)
leaves: ['pk(@carol)', 'pk(@alice)'] }, (2)
amount: 49.999.sats
}
]
1 | Output can be spent using tweaked internal key |
2 | Alternatively, the output can be spent using Carol’s or Alice’s untweaked keys |
Example
The examples below spend the above taproot output. First using the keypath and then using the script path.
Spending via the Keypath
To spend via a keypath, we need provide the private key that the DSL then tweaks for the taproot output. The developer doesn’t need to track the taproot construction anymore, the DSL looks at the taproot output and figures out the tweak to apply.
transition :spend_using_internal_key do
@spend_taproot_output_tx = transaction inputs: [
{ tx: @taproot_output_tx,
vout: 0,
script_sig: { keypath: @bob }, (1)
sighash: :all }
],
outputs: [
{ descriptor: 'wpkh(@carol)',
amount: 49.998.sats }
]
broadcast @spend_taproot_output_tx
confirm transaction: @spend_taproot_output_tx
log 'Taproot output spent using keypath'
end
1 | Provide the key pair or a private key and DSL tweaks the private key before signing the transaction |
Spending via Script Path
To spend a taproot output using one of the leaves, we simply need to supply the unlocking script using DSL extension.
transition :spend_first_leaf do
@spend_taproot_output_tx = transaction inputs: [
{ tx: @taproot_output_tx,
vout: 0,
script_sig: { leaf_index: 0, (1)
sig: 'sig:@carol' }, (2)
sighash: :all }
],
outputs: [
{ descriptor: 'wpkh(@carol)',
amount: 49.998.sats }
]
broadcast @spend_taproot_output_tx
confirm transaction: @spend_taproot_output_tx
log 'Taproot script path transaction spent using first leaf'
end
1 | Identify the leaf being spent |
2 | Provide script sig using DSL sig extension |