NAME

Bitcoin::Crypto::Manual::Taproot - Taproot support details

DESCRIPTION

This page describes all the various bits and pieces that are used in Bitcoin::Crypto's taproot support.

Keys and derivation

Taproot is implemented using the same key classes as before. You can use purpose 86 or Bitcoin::Crypto::Constants::bip44_taproot_purpose to BIP44-derive extended keys in compilance with BIP86. "get_address" in Bitcoin::Crypto::Key::Public now returns taproot addresses by default.

Bitcoin::Crypto's taproot handling should be opaque, so that you don't need to manually handle xonly public keys, taproot output keys and schnorr signatures. Standard keypair of Bitcoin::Crypto::Key::Private and Bitcoin::Crypto::Key::Public should be all you need most of the time.

Constructing addresses

Method "get_taproot_address" in Bitcoin::Crypto::Key::Public works like other address-generating methods, but can optionally accept an additional argument to support spending via a script. Taproot scripts can simultaneously be spendable using key path (like P2WPKH) or script path (like P2WSH). If you omit the extra argument, coins sent to address will only be spendable with the key path.

To enable script path, the extra argument to the method mentioned above must be an instance of Bitcoin::Crypto::Script::Tree (shortcut "btc_script_tree" in Bitcoin::Crypto). Script trees are explained in BIP341 - they are binary trees with each leaf being a single script. When spending a taproot output, any script from a tree can be selected, and only this script must be revealed. All other scripts in the tree will stay private.

Currently, all leaves in the tree must have leaf_version equal to 0xc0 or Bitcoin::Crypto::Constants::tapscript_leaf_version, according to BIP342. Using this leaf_version means that script in the leaf must be an instance of Bitcoin::Crypto::Tapscript (shortcut "btc_tapscript" in Bitcoin::Crypto). Tapscripts are similar to scripts, but they use a different set of opcodes (Bitcoin::Crypto::Tapscript::Opcode).

To completely disable spending via key path (to force spending with scripts), "Nothing Up My Sleeve" Bitcoin::Crypto::Key::NUMS can be used, which generates public keys that have no known private key counterparts.

Spending taproot outputs

Signing transactions by using key path spend is as simple as calling "sign_transaction" in Bitcoin::Crypto::Key::Private on a transaction with proper signing_index. Signing spend by script path is much harder and currently has no internal implementation. Here's a naive example of witness stack construction that uses sighash_all and assumes no OP_CODESEPARATOR in the script:

my $tree = btc_script_tree->new(tree => [{id => 0, script => $script, leaf_version => 0xc0}]);
my @input_witness;

# first witness element: signature for script [<pubkey> OP_CHECKSIG]
push @input_witness, $privkey->sign_message(
	$tx->get_digest(
		signing_index => 0,
		signing_subscript => $script->to_serialized,
		taproot_ext_flag => 1,
		taproot_ext => get_taproot_ext(1, script_tree => $tree, leaf_id => 0),
		sighash => Bitcoin::Crypto::Constants::sighash_all,
	)
) . pack('C', Bitcoin::Crypto::Constants::sighash_all);

# second witness element: serialized script
push @input_witness, $script->to_serialized;

# third witness element: taproot control block
push @input_witness, $tree->get_control_block(0, $pub)->to_serialized;

This example uses "get_taproot_ext" in Bitcoin::Crypto::Util to build extension defined in BIP342. "get_control_block" in Bitcoin::Crypto::Script::Tree returns an instance of Bitcoin::Crypto::Transaction::ControlBlock. After serialization, control block will contain the data required for the transaction to reconstruct the script tree and validate the used script was a part of it.

To be able to spend via script path you need to have the exact copy of script tree saved somewhere. Bitcoin::Crypto::Script::Tree class has no serialization method, but Bitcoin::Crypto::PSBT with PSBT_OUT_TAP_TREE field can be used to store the entire unhashed script tree in base64 format.

Various bits and pieces

  • Public keys in scripts should be taproot output keys. These are xonly keys after taproot tweaking, so they use a different way of serialization. Use sequence $pubkey->get_taproot_output_key->get_xonly_key to get the tweaked output key. Alternatively, to avoid tweaking and use the key as-is, call $privkey->set_taproot_output(1) before signing and use $pubkey->get_xonly_key instead.

  • To create a public key from 32-byte xonly public key, use "lift_x" in Bitcoin::Crypto::Util by calling btc_pub->from_serialized(lift_x $bytes). Call set_taproot_output on it if it is supposed to be a taproot output key (e.g. used in schnorr signatures).

  • Encountering one of the new OP_SUCCESS opcodes while compiling a tapscript will mark it as unconditionally valid. "operations" in Bitcoin::Crypto::Script of such script will appear empty, but calling "success" in Bitcoin::Crypto::Script::Runner on the runner which compiled it will return true.