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 )