Bitcoin-Crypto

 view release on metacpan or  search on metacpan

ex/tx/taproot_script_redeem.pl  view on Meta::CPAN

use v5.14;
use warnings;

use Bitcoin::Crypto qw(btc_psbt btc_transaction btc_utxo btc_prv btc_script_tree btc_tapscript);
use Bitcoin::Crypto::Util qw(to_format);
use Bitcoin::Crypto::Network;

Bitcoin::Crypto::Network->get('bitcoin_testnet')->set_default;

# this is PSBT generated by taproot_script_create.pl script
my $psbt = btc_psbt->from_serialized(
	[
		base64 =>
			'cHNidP8BAF4BAAAAASrw4x+sPfBApDwbeArERu8uYmQEVAQgnrLD8fA6tpsmAAAAAAD/////AaifBwAAAAAAIlEgSOoBPq0tYMqcvx0BPYScIeRsE/xTJQ9Ut58kC3OphC8AAAAAAAABBSC66lPZr8m73ZKZvmN2kATfEefN1nYUpXX5lOJRGdz3/QEGcQHAaQAgBq4mEWYxDxsfKhKphM8+BCROC1d5lUMuFVsq8n63WCK6IOWo3Y...
	]
);

# extract outputs from psbt transaction
my $prev_tx = $psbt->get_field('PSBT_GLOBAL_UNSIGNED_TX')->value;
$prev_tx->update_utxos;

# get tree from psbt, mark the leaf we're spending with an id, and clear tree
# cache (required after manual changes to tree structure)
my $tree = $psbt->get_field('PSBT_OUT_TAP_TREE', 0)->value;
$tree->tree->[0]{id} = 0;
$tree->clear_tree_cache;

# get public key from psbt
my $public_key = $psbt->get_field('PSBT_OUT_TAP_INTERNAL_KEY', 0)->value;

my $tx = btc_transaction->new;

# input must point to the transaction output above - transaction ID and output number
$tx->add_input(
	utxo => [$prev_tx->get_hash, 0],
);

# send all the coins to this address.
$tx->add_output(
	locking_script => [address => 'tb1plhfdt6d2ngfsl7zn0rd59pfz87ynqj5wvmttxwy4xxn2zkcregkqdvkalv'],
	value => 0,
);

# calculate fee and set the value of first output. Unsigned tx virtual size is
# used, so the real fee rate will be approx two times smaller
my $wanted_fee_rate = 2;
$tx->outputs->[0]->set_value($tx->fee - int($tx->virtual_size * $wanted_fee_rate));

# semi-manual signing (since the transaction is custom):
# - signing with two private keys to satisfy 2 out of 3 transaction
# - leaving one empty signature for the one signature we don't use (second item)
# - signing with SIGHASH_DEFAULT, so it does not need to be passed explicitly
# - not adding annex, since it has no meaning yet (can lead to funds loss)

my $private_key_1 = btc_prv->from_wif('L2eKy3kX5DYnw7B1sXpEs2gd9xK5PSkiiBS1YSFaHvYyn1M9rsJJ');
my $private_key_3 = btc_prv->from_wif('L2zrD2aQRgGHzJpX7TB7qYhzibFqitH3NyvZUJUzqfHaLmxyBvm5');

# we want to use these keys for schnorr signatures, so we mark them as taproot outputs
$private_key_1->set_taproot_output(!!1);
$private_key_3->set_taproot_output(!!1);

# use sign to build a witness semi-manually
$tx
	->sign(
		signing_index => 0,
		script_tree => $tree,
		leaf_id => 0,
		public_key => $public_key,
	)
	->add_signature($private_key_1)
	->add_signature('')
	->add_signature($private_key_3)
	->finalize;

# verify the correctness of the transaction. Throws an exception on failure
$tx->verify;

say $tx->dump;
say to_format [hex => $tx->to_serialized];

__END__

=head1 P2TR script output redeem example

A transaction spending one P2TR input with script spend path and produces a
single P2TR output. Example uses "Nothing up my sleeve" point and script tree
generated previously, which is imported from a PSBT base64 string.

We choose the first script in the tree, which uses new C<OP_CHECKSIGADD> opcode
to implement a 2 out of 3 multisig script. Taproot has old C<OP_CHECKMULTISIG>
opcode disabled.

This example shows how to sign a custom taproot transaction.

Fee rate is (inaccurately) approximated. To set exact fee rate sign the
transaction, calculate fee based on its virtual size and then sign again
- changing the value of the output invalidates previous signatures.

This code was used to produce testnet transaction:
L<https://mempool.space/testnet4/tx/d87c30f149aae524296ea39b6f2db2d6361981bdde36ae1862e946910be9ea0b>



( run in 2.309 seconds using v1.01-cache-2.11-cpan-13bb782fe5a )