NAME

Bitcoin::Crypto::Manual - Main reference to Bitcoin::Crypto

DESCRIPTION

Bitcoin::Crypto is a mature cryptographic Perl module for Bitcoin management. It focuses on delivering unopinionated tools which make it possible to manage Bitcoin with Perl code. The following list of topics is an overview of module's capabilities:

  • keypair management, key derivation, address generation

  • block and transaction building and management

  • signing and verifying transactions

  • script building and execution

  • Bitcoin-related formats: PSBT, bech32, base58, mnemonics

This document serves as a reference to working with Bitcoin::Crypto. It is not complete on its own, but it should refer you to other documentation pages with more detailed information about the given subject.

Bitcoin overview

Bitcoin is a decentralized ledger of transactions. Transactions come in a sequence, one after another. This sequence is split into chunks, called blocks. Blocks are generated in a process called mining, which tries to randomly find a block with a certain hashed value. That value is adjusted periodically, so that each block is mined every 10 minutes (on average). The entire point of mining is to prove that a significant amount of time and energy was spent finding a block. This is called Proof of Work (PoW), and is a way to gain trust in a trustless, decentralized system. Miner who found a block has the right to inject a limited number of new coins into the system, which is how all coins in existence were created.

Transaction is the single most important structure in Bitcoin. It defines where the coins came from (inputs) and where they will end up (outputs). Transaction can have multiple inputs and multiple outputs. Each input must point to an output of a previous transaction, and spend it entirely. Once the output is used in a transaction, it is no longer considered an UTXO (unspent transaction output), and can't be spent again. Since UTXOs must be spent entirely at once, any coins exceeding the payment amount must be sent to outputs controlled by the sender.

Bitcoin script is a language for defining spend conditions for outputs. Each transaction output has a locking script which must yield success when the transaction is verified. Transaction inputs define initial conditions for execution of locking script, in form of signature script or witness. Vast majority of output scripts contain a public key and signature checking against the serialized form of the transaction.

To prove ownership of coins, Bitcoin uses asymmetric cryptography over a secp256k1 Elliptic Curve. Most bitcoin locking scripts are standard type scripts, which can be recognized as addresses. Each private/public keypair corresponds to a single address. An example of a standard script is P2TR ("pay to taproot"), and its locking script can be encoded as a bech32 address starting with bc1p. That address contains the public key, and spending coins sent to it requires a signature from the corresponding private key. Once a transaction creates an UTXO to an address corresponding to a private/public keypair, the coins are considered to be owned by the person who controls the private key.

Whitepaper and Bitcoin Improvement Proposals

Original Bitcoin was proposed and implemented by Satoshi Nakamoto. The first document which defines how Bitcoin works (without much detail) is called the Bitcoin whitepaper. Since Bitcoin inception, the code has been modified heavily, and most influential changes were introduced as Bitcoin Improvement Proposals (BIPs).

Each BIP has its unique number and defines one change or new feature. It is very common to use some BIP numbers to refer to that change or feature. All BIPs can be viewed in their GitHub repository: https://github.com/bitcoin/bips.

Storing and accessing secrets

The most widely used method of storing private keys for Bitcoin is mnemonic phrases, which are sets of 12 to 24 human-readable words. Mnemonics can be imported and turned into an extended key, which can be used to derive an unlimited number of addresses. Mnemonics can be stored securely away from devices connected to the Internet, or without a computer at all, for example written on a piece of paper - this is called cold storage. Secrets stored on a computer connected to the Internet are called hot storage, and have a greater than zero chance to be hacked. If possible, cold storage should be used for storing large sums, while hot storage can be used as "pocket money". Certain hardware wallets exist, which are not connected to the Internet, but can be connected via a private channel and asked to sign a transaction.

Key derivation uses standardized paths, referred to as BIP44. This standardization allows different wallet software to access the coins stored on a mnemonic. Such interoperability is a great strength of this solution, since it is easy to switch software vendor if needed. Keys are derived using Hierarchical-Deterministic algorithm, and they are generated in a sequence according to fund discovery algorithms. This ensures no coins will be lost within a mnemonic, as the same keys can be generated again deterministically.

General information

Installation

This module requires you to have access to a compiler to install some of its dependencies. You are also required to have libsecp256k1 installed. If it isn't, Alien::libsecp256k1 will attempt to download and install it automatically.

Backward compatibility

Any behavior which is not explicitly documented is considered internal and may be changed without a deprecation period. This applies to: undocumented methods or functions, undocumented return values, undocumented side effects. Lacking documentation is therefore considered a bug and will be treated as such when reported.

Developers of Bitcoin::Crypto promise not to break existing documented API without a good reason and a long deprecation period (at least one year). We value backward compatibility and consider it one of the greatest strengths of Perl. However, we reserve the right to break it in the following cases:

  • if breaking backcompat is required to fix a serious bug in function's behavior

  • if the function is documented as "advanced use only"

  • if the function was first introduced within one month of changing it (hotfixing)

Exceptions

This module tries hard not to throw any unblessed exceptions. Most exceptions should be caught and turned into a subclass of Bitcoin::Crypto::Exception, or the main class if no specific subclass exists for the exception. However, unblessed errors may still happen occasionally.

Refer to "EXCEPTION SUBCLASSES" in Bitcoin::Crypto::Exception for a list of exceptions used by the module.

Note that removing completely subclass of exceptions is not considered backward-incompatible, as checking for non-existent class through isa does no harm. Renaming an exception subclass or merging it into another subclass is an incompatibility and will be done only after a deprecation period.

Class shortcuts

The most frequently used classes have their shortcuts starting with btc_ registered in Bitcoin::Crypto. Examples are btc_prv for basic private key or btc_transaction for transactions. These shortcuts can make the code more compact and avoid manually requiring too many classes, but they are completely optional. If this style does not sound reasonable to you, feel free to ignore it.

See "SHORTCUT FUNCTIONS" in Bitcoin::Crypto for details.

How to pass data to functions?

Many frequently used functions (like from_serialized commonly used in keys, scripts and transactions) require you to pass in a bytestring. Bytestring is a string in which each character has numeric value less than or equal to 255. It is the default way to pass in data to functions in Bitcoin::Crypto.

It is common that you may want to use some other format instead, like hex strings. To avoid duplicating functions for different formats or manually transforming data to a bytestring, the module uses data structures called format descriptions. In any place where a bytestring is used you may instead use an array reference with exactly two elements. The first element must be a name of the format, and the second element is the actual data in that format. For strings of hex data, this may look like this:

use Bitcoin::Crypto qw(btc_prv);
my $private = btc_prv->from_serialized([hex => '152a3f549597a2bef783']);

Currently supported values for the first argument are:

  • hex (hexadecimal string, may be prefixed by 0x)

  • base58 (base58-encoded string with the checksum)

  • base64 (base64-encoded string)

It is also common for functions to return bytestrings (like to_serialized). If you need help changing that output format you may use "to_format" in Bitcoin::Crypto::Util helper function, which does the reverse operation:

use Bitcoin::Crypto::Util qw(to_format);
print to_format [hex => $private->to_serialized];

How to pass commonly used script types to functions?

Similar to format descriptions, you may use a script description anywhere a script is expected. It is an array reference of two elements, where the first one is the short name of the script type, like P2WPKH. The second one contains data specific to that script type, usually the address:

$transaction->add_output(
	locking_script => [P2WPKH => 'bc1qr9htu5sy02q6kv6mx7axz2zdg3k9nrh8pe4l47'],
);

You may also leave address detection up to the module by using the string address:

# the same thing
$transaction->add_output(
	locking_script => [address => 'bc1qr9htu5sy02q6kv6mx7axz2zdg3k9nrh8pe4l47'],
);

Note that the script created like this always belongs to the currently set default network (bitcoin by default). Passing address from the wrong network will result in an exception.

See "from_standard" in Bitcoin::Crypto::Script for more details.

Support for other cryptocurrencies

Bitcoin::Crypto has limited support for other cryptocurrency networks through Bitcoin::Crypto::Network. The network must be fully compatible with Bitcoin (a Bitcoin clone or a strict subset of Bitcoin features) - no extra cases will exist in the code, only constant values can differ between the networks. Lack of certain constant values can be used to mark feature unsupported in the given network, for example lack of segwit_hrp network constant will be interpreted as network not supporting Segregated Witness. Note that Bitcoin testnet is also considered a separate network.

Working with Bitcoin::Crypto

There are many goals which you may want to achieve with this module. It's hard to show a single learning path that will cover all use cases. Instead, common topics of interest are listed below, from basic to advanced.

Selecting a network

Bitcoin::Crypto::Network allows you to choose the network, which has influence on some parts of the system, like generated addresses. If you want to work with default Bitcoin Mainnet, there is no need to select it manually. See "PREDEFINED NETWORKS" in Bitcoin::Crypto::Network for a list of networks which can be used out of the box, without extra configuration.

use Bitcoin::Crypto::Network;

# put module into single-network mode and choose testnet
Bitcoin::Crypto::Network->get('bitcoin_testnet')->set_single;

Networks can be set globally through default network or on per-object basis for basic and extended keys, and scripts. Other parts of the system does not support per-object setting of networks and rely on the currently set default network. Setting keys for every object separately is considered an outdated feature which is kept for backwards compatibility (and convenience, sometimes). If you want to make sure all your keys are in the same, default network, see single-network mode in "single_network" in Bitcoin::Crypto::Network, as shown above. This mode will disallow creation of objects with a network other than the default. It can come handy if you are de-serializing keys from non-constant strings, for example fetched from the Internet or entered by the user.

Although it isn't required when interacting with the module in a normal way, encodings like Bitcoin::Crypto::Base58 and Bitcoin::Crypto::Bech32 can be used standalone for custom purposes.

use Bitcoin::Crypto::Bech32 qw(encode_bech32);

# encode some data as bech32
my $encoded_str = encode_bech32('customdata', [25, 13, 31, 8, 0, 5]);

You can validate an address and get its type using "get_address_type" in Bitcoin::Crypto::Util. Wallet Import Format (WIF) strings can be validated by "validate_wif" in Bitcoin::Crypto::Util. When handling Bitcoin-related raw data, "pack_compactsize" in Bitcoin::Crypto::Util and "unpack_compactsize" in Bitcoin::Crypto::Util will prove themselves useful.

Generating and importing a mnemonic

Use "generate_mnemonic" in Bitcoin::Crypto::Util to generate list of words which form a mnemonic, and "from_mnemonic" in Bitcoin::Crypto::Key::ExtPrivate to turn it into an instance of extended private key. Mnemonic can be imported with a password, which generates a different mnemonic. Beware that the password cannot be recovered in any way (other than brute forcing) if it is forgotten.

use Bitcoin::Crypto qw(btc_extprv);
use Bitcoin::Crypto::Util qw(generate_mnemonic);

# generate a mnemonic and import it
my $mnemonic = generate_mnemonic;
my $master_key = btc_extprv->from_mnemonic($mnemonic, $password);

Deriving from a master key

The key obtained by importing a mnemonic is called a master key and should be derived before using it. The most common way of deriving it is BIP44, which can be performed by using "derive_key_bip44" in Bitcoin::Crypto::Key::ExtPrivate. The way BIP44 works is it defines purposes (the context of derivation - what type of address is generated), accounts (arbitrary namespace for keys, which is a sequential number and can be increased at any time) and indexes (consecutive receiving addresses within an account). A key for account can be derived, which will then allow deriving multiple keys later.

Here, we will get the extended key for the first account (account 0 is implied) to be used with Segregated Witness addresses:

use Bitcoin::Crypto::Constants qw(:bip44);

my $account_key = $master_key->derive_key_bip44(
	purpose => BIP44_SEGWIT_PURPOSE,
	get_account => !!1,
);

Obtaining a basic keypair from account

Bitcoin basic private and public keys are essential for generating addresses and signing transactions.

Here, we will get the first index from our account key (index 0 is implied) and get an address from it. Since we specified BIP44_SEGWIT_PURPOSE earlier, the address will be of type P2WPKH, which is a native SegWit address.

# get the first index key from account key
my $extended_key = $account_key->derive_key_bip44(
	get_from_account => !!1,
);

# get the basic key from extended key
my $private = $extended_key->get_basic_key;

# generate an address
my $public = $private->get_public_key;
say $public->get_address;

You can also import a private key from serialized forms, like WIF, though it is not as common as getting it from a mnemonic. See Bitcoin::Crypto::Key::Private for more details.

Generating entropy from mnemonic deterministically

Another cool use of mnemonics is generating entropy from them deterministically. Bitcoin::Crypto::BIP85 lets you derive mnemonics, private keys or just entropy from a single extended private key.

use Bitcoin::Crypto::BIP85;

my $generator = Bitcoin::Crypto::BIP85->new(
	key => $master_key,
);

# generate ten new mnemonics - this yields deterministic results
for my $i (0 .. 9) {
	my $new_mnemonic = $generator->derive_mnemonic(index => $i);
	say $new_mnemonic;
}

Creating a Bitcoin script

Bitcoin::Crypto::Script will help you build, serialize, deserialize and run a script. Bitcoin::Crypto::Script::Runner gives you more control over script execution, including running in the context of a certain transaction object, or running the script step by step, allowing introspection after each opcode.

use Bitcoin::Crypto qw(btc_script);

my $bytestring = "\x00" x 10;
my @initial = ($bytestring);

# create a script by hand and run it with an initial stack
my $runner = btc_script->new
	->add('OP_SIZE')
	->push_number(10)
	->add('OP_NUMEQUAL')
	->run(\@initial);

# check if the script succeeded
say 'success' if $runner->success;

Deserializing a block

Bitcoin::Crypto::Block is a class which can be used to deserialize a raw Bitcoin block. Deserializing automatically verifies the merkle root of a block. All transactions created this way are set as belonging to the block.

use Bitcoin::Crypto qw(btc_block);

# deserialize and dump a block
my $block = btc_block->from_serialized($raw_block_data);
say $block->dump;

Transactions

First and foremost, understand that in its nature Bitcoin::Crypto transaction system is just a reimplementation. Reimplementations are known to have bugs which can make them insecure to be used as a full node - see https://bitcointalk.org/index.php?topic=260595.0. In addition, it is rather ineffective to verify the entire blockchain using Perl + XS code. We focus on support for standard transaction types (see below). While we strive to be bug-free eventually, it's good to be realistic about capabilities of this module in real-life scenarios. Be aware of the "DISCLAIMER".

Which types of transactions are supported?

Bitcoin::Crypto test suite contains pretty thorough testing of the following output types:

  • P2PK (pay to public key - obsolete)

  • P2PKH (pay to public key hash - legacy)

  • P2SH (pay to script hash - legacy)

  • P2WPKH (pay to witness public key hash)

  • P2WSH (pay to witness script hash)

  • P2SH(P2WPKH) (P2WPKH inside P2SH)

  • P2SH(P2WSH) (P2WSH inside P2SH)

  • P2MS (pay to multisig, usually nested inside P2SH or P2WSH)

  • P2TR (pay to taproot, both key and script path spends)

  • NULLDATA (provably unspendable outputs with OP_RETURN)

Be aware that Bitcoin::Crypto is required to pass pretty comprehensive Bitcoin Core tests suites for script execution and transaction verification before each release, so it should be supporting script of any complexity just fine. However, be extra careful when writing your own scripts, as the chance of making a mistake is much higher than when using standard key spending transactions.

How we handle the UTXOs

Normally, when using software which is working as a full node (such as Bitcoin Core) the blockchain history can be accessed without any extra steps, because they collect full blockchain data. Bitcoin::Crypto does not do that and depend on you to provide valid Unspent Transaction Outputs (UTXOs) before adding inputs to the transaction (or deserializing a transaction). You can get transaction data from your own Bitcoin full node or from external sources such as https://mempool.space. UTXO data is necessary for performing many tasks on transactions, like calculating the fee, signing or verifying.

You can register UTXOs manually using btc_utxo->new(...)->register or automatically from a serialized transaction using btc_utxo->extract. See "register" in Bitcoin::Crypto::Transaction::UTXO and "extract" in Bitcoin::Crypto::Transaction::UTXO for details. The process of getting transaction data can be automated using the "set_loader" in Bitcoin::Crypto::Transaction::UTXO hook (for example, call some API to get the serialized transaction and use extract on it).

This means you can create valid transactions without the need to download, store and verify the entire Bitcoin blockchain. You only need to know which UTXOs belong to you. Once you have created a transaction and it got validated in the blockchain, you can call $tx->update_utxos on it, which will invalidate the UTXOs it used and register its outputs as new UTXOs, which will reflect the changes that happened on the chain.

Bitcoin::Crypto implements blocks as Bitcoin::Crypto::Block. While transactions don't usually care about blocks at all, their objects can be passed as block attribute in "new" in Bitcoin::Crypto::Transaction and "new" in Bitcoin::Crypto::Transaction::UTXO. Blocks can be used partially (without transactions) for the purpose of checking locktime or sequence of a transaction. Not specifying a block for a transaction will mean that locktime and sequence checks will always pass (and raise a warning).

There is no way to set network (Bitcoin::Crypto::Network) on per-transaction-object basis like you can do with keys. Most of transaction functionality is not network-dependent, but some addresses will be encoded using given network settings, which will be pulled from the default network. Note that only networks which use the exact consensus rules of Bitcoin can be used successfully. No special cases for other networks will be implemented.

Consensus vs standard

Bitcoin validation rules are either enforced on consensus level (transaction is invalid if violated) or standardness level (transaction is rejected by mempool when violated). Transactions which follow the protocol but not the standard would still be valid if they were inserted into the block directly by the miner (skipping the mempool).

Flags for certain consensus or standard rules are located in Bitcoin::Crypto::Transaction::Flags. By default, "verify" in Bitcoin::Crypto::Transaction uses only consensus rules, which can be dangerous to depend on, because it may make it much harder to spend such transaction. For complete check of standard rules, "verify_standard" in Bitcoin::Crypto::Transaction can be used, which constructs flags object with all flags active.

Creating a transaction object from scratch

This extremely simple example creates a transaction with a single input and a single output. The transaction is not signed, and if the UTXO for the input was not yet registered, it will not be possible to do much with it other than inspecting it (as it will raise an exception when trying to fetch the UTXO).

use Bitcoin::Crypto qw(btc_transaction);

# create a transaction
my $tx = btc_transaction->new;

$tx->add_input(
	utxo => [[hex => '9dd8d1ebba95d4ddc2b9fa21b9bc893385e9d5928d0d4835433f34b0ad4b9527'], 0],
);

$tx->add_output(
	locking_script => [address => 'bc1p857ug9whvzapyhkxtjsha0376y2w2yc2dnegx4jlaqcftrfmnt0shkqj6h'],
	value => 1337,
);

# dump it in human-readable form
say $tx->dump;

Importing the UTXOs

To be able to calculate fee, sign or verify the transaction, we must first import the UTXO for our input. We can do this with Bitcoin::Crypto::Transaction::UTXO. This could be done automatically if we called "extract" in Bitcoin::Crypto::Transaction::UTXO on a serialized transaction, but let's just build it by hand.

use Bitcoin::Crypto qw(btc_utxo);

my $utxo = btc_utxo->new(
	txid => [hex => '9dd8d1ebba95d4ddc2b9fa21b9bc893385e9d5928d0d4835433f34b0ad4b9527'],
	output_index => 0,
	output => {
		locking_script => [address => 'bc1pevxtvxmv9m2p202t9kcavhgfv2utcpy4zhpvxxqx6zcjll4f0c4ss6c2hy'],
		value => 1500,
	},
);

# make this UTXO known to transactions
$utxo->register;

After the call to "register" in Bitcoin::Crypto::Transaction::UTXO, we can now call "fee" in Bitcoin::Crypto::Transaction to calculate the fee. Without calling register, it would raise an exception, because the previous output value was not known.

Automatically signing a transaction

Since we created the UTXO as a P2TR output (recognizable by the prefix bc1p), we can automatically sign it with a private key:

# sign the first (and only) input
$private->sign_transaction($tx, signing_index => 0);

# verify if it worked - will raise an exception on failure
$tx->verify_standard;

Manually signing transactions

Lets assume a different scenario: we have a completely custom script which was embedded in a SegWit compatibility P2WSH script (P2SH(P2WSH)). The script looks like this:

my $script = btc_script->new
	->push_number(1)
	->add('OP_EQUAL')
	->add('OP_IF')
		->push($serialized_external_public1)
		->add('OP_CHECKSIG')
	->add('OP_ELSE')
		->push_number(2)
		->push($public->to_serialized)
		->push($serialized_external_public2)
		->push_number(2)
		->add('OP_CHECKMULTISIG')
	->add('OP_ENDIF');

This script conditionally checks either one signature, or a 2-of-2 multisignature, based on the value of the first argument. Lets assume we own the private key to $public, and $serialized_external_public1 and $serialized_external_public2 are owned by others. We are spending multisignature branch, and the owner of that key has already signed the transaction and handed the signature to us. Our job is to sign this transaction and broadcast it. How can we do it?

After we build the transaction $tx (same procedure as in "Creating a transaction object from scratch"), we must sign it manually by calling "sign" in Bitcoin::Crypto::Transaction. We must use compat flag to mark it as a compat SegWit, and provide the script object.

# get the signer object
my $signer = $tx->sign(
	signing_index => 0,
	script => $script,
	compat => !!1,
);

Object $signer is an instance of Bitcoin::Crypto::Transaction::Signer, and lets us build our signature step by step. In our case, signing it should look like this:

$signer
	->add_number(0)                      # enter the branch with OP_CHECKMULTISIG
	->add_signature($external_signature) # add a known external signature
	->add_signature($private)            # generate a signature using our private key
	->finalize_multisig                  # end the OP_CHECKMULTISIG signing
	->finalize;                          # apply changes to the transaction

Note that even though in the script our public key came in first, we must reverse the order and add a signature for it as the second one. This is required because OP_CHECKMULTISIG will remove them from initial stack in the reverse order (according to LIFO rule).

When finalize is called, the transaction is modified and it should now contain a valid signature (but always check with verify or verify_standard).

More transaction examples

Bundled directory ex/tx/ contains scripts with transactions which were published to the testnet chain. These scripts should provide a convenient starting base for anyone interested in hacking Bitcoin using Bitcoin::Crypto.

Each example contains a short description and a link to the transaction in the blockchain explorer. The fact that these were successfully processed by the network is a good testament to module's faithfulness to consensus rules. However, we still strongly encourage using other tools or otherwise testing the transactions before broadcasting them to the network.

Known problems with transactions

Issues with transaction verification algorithms are listed below. Contributions sorting out any of those are welcome!

  • Sigop limit for pre-taproot transactions is not checked

    Before taproot, limit of signature operations was defined for a block, not for a transaction. We currently do not check that.

Taproot

Taproot extends bitcoin functionality to add one new type of address (P2TR), xonly public keys (32-byte public keys with only X coordinate), schnorr signatures, script trees and tapscripts (along with new OP_CHECKSIGADD). Taproot outputs can be spent using key path (like P2PKH) or script path (like P2SH). There is no visual distinction between key path and script path spending before outputs are spent. In addition, taproot outputs can contain many scripts organized in a tree, and any single script can be used for spending without the need to reveal all scripts in the tree. Overall, taproot increases privacy, increases flexibility of scripts and reduces the size of transactions, which results in lower fees.

Keys and derivation

Taproot distinguishes between two types of keys: internal keys and output keys. Internal keys are like the regular ECC keypair, but the public key is serialized as 32 byte array xonly key (dropping the first byte of regular compressed serialization format). Output keys are internal keys that were tweaked with a taproot-specific procedure. Bitcoin::Crypto uses the same Bitcoin::Crypto::Key::Private and Bitcoin::Crypto::Key::Public instances for both internal and output keys. The difference is that output keys are marked with taproot_output flag and uses schnorr algorithm for signing and verifying.

You can use purpose number 86 or "BIP44_TAPROOT_PURPOSE" in Bitcoin::Crypto::Constants to BIP44-derive extended keys in compliance with BIP86.

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 should be all you need most of the time.

Constructing addresses

"get_address" in Bitcoin::Crypto::Key::Public returns taproot addresses by default. 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 "TAPSCRIPT_LEAF_VERSION" in Bitcoin::Crypto::Constants, 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.

Building script trees

Bitcoin::Crypto::Script::Tree lets you create tapscript trees and utilize them in transactions. Every script spend requires a taproot tree, even if it contains just a single script.

use Bitcoin::Crypto::Constants qw(:script);
use Bitcoin::Crypto qw(btc_tapscript btc_script_tree);

my $script1 = btc_tapscript->new
	->push($pubkey1)
	->add('OP_CHECKSIG');

my $script2 = btc_tapscript->new
	->add('OP_0')
	->push($pubkey2)
	->add('OP_CHECKSIGADD')
	->push($pubkey3)
	->add('OP_CHECKSIGADD')
	->add('OP_2')
	->add('OP_EQUAL');

my $script3 = btc_tapscript->new
	->add('OP_7')
	->add('OP_EQUAL');

my $tree = btc_script_tree->new(
	tree => [
		{
			id => 0,
			leaf_version => TAPSCRIPT_LEAF_VERSION,
			script => $script1,
		},
		[
			{
				id => 1,
				leaf_version => TAPSCRIPT_LEAF_VERSION,
				script => $script2,
			},
			{
				id => 2,
				leaf_version => TAPSCRIPT_LEAF_VERSION,
				script => $script3,
			},
		]
	],
);

my $merkle_root = $tree->get_merkle_root;

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.

Notice how each leaf in the tree can have an id. This is the way Bitcoin::Crypto locates the leaf in various places, for example in "get_taproot_ext" in Bitcoin::Crypto::Util or in "leaf_id" in Bitcoin::Crypto::Transaction::Signer. However, since it is a custom part of this structure which is not serialized in PSBT, often you have to give an ID to a structure which is already created. In this case, you can add an id directly into a structure in "tree" in Bitcoin::Crypto::Script::Tree, and then call "clear_tree_cache" in Bitcoin::Crypto::Script::Tree:

$tree->tree->[0]{id} = 0;
$tree->clear_tree_cache;

Various bits and pieces

  • Public keys in scripts should be marked as taproot_output keys. These are xonly keys, so they use a different way of serialization. Use sequence $pubkey->get_taproot_output_key->get_xonly_key to get the tweaked output key as a bytestring. 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. This may be useful if you obtain a serialized copy of a key that was already tweaked or used for schnorr signatures.

  • 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 may not be very useful after the first OP_SUCCESS (may contain garbage), but calling "success" in Bitcoin::Crypto::Script::Runner on the runner which compiled it will return true.

Partially Signed Bitcoin Transactions

PSBT is a format for exchanging transaction data between parties in a standardized way. Each PSBT consists of maps, which are containers for values. There are Global, Input and Output maps, and each consists of various fields which can be used to store data. Fields each have an optional key and a value.

Bitcoin::Crypto implements PSBT in Bitcoin::Crypto::PSBT. It contains implementations of all fields from BIP174 (PSBTv0), BIP370 (PSBTv2) and BIP371 (taproot fields). Our implementation uses serializers and deserializers to map binary PSBT data into Bitcoin::Crypto objects.

Following example (taken from ex/tx/taproot_script_create.pl) creates a minimal PSBTv0 to store a taproot script tree and a NUMS public key. It depends on serializers to turn Perl objects into binary data:

use Bitcoin::Crypto qw(btc_psbt);

# create a PSBT with tree and public key saved for later
my $psbt = btc_psbt->new;
$psbt->add_field(
	type => 'PSBT_GLOBAL_UNSIGNED_TX',
	value => $tx,
);
$psbt->add_field(
	type => 'PSBT_OUT_TAP_INTERNAL_KEY',
	value => $public,
	index => 0,
);
$psbt->add_field(
	type => 'PSBT_OUT_TAP_TREE',
	value => $tree,
	index => 0,
);

say 'PSBT with tree and internal key: ' . to_format [base64 => $psbt->to_serialized];

Following example (taken from ex/tx/taproot_script_redeem.pl) decodes a that PSBT and pulls data out of it:

my $psbt = btc_psbt->from_serialized([base64 => $b64_psbt]);

my $prev_tx = $psbt->get_field('PSBT_GLOBAL_UNSIGNED_TX')->value;
my $tree = $psbt->get_field('PSBT_OUT_TAP_TREE', 0)->value;
my $public_key = $psbt->get_field('PSBT_OUT_TAP_INTERNAL_KEY', 0)->value;

See Bitcoin::Crypto::PSBT for more details and a complete reference of field types and what objects their serializers / deserializers use.

Performance

A lot of work has been done to ensure Bitcoin::Crypto transaction decoding and verification performance is somewhat acceptable. Benchmarks on author's (not very powerful) machine have shown that up to 600 legacy transactions (each having 2 P2MS inputs) or 800 taproot transactions (each having 4 key spend inputs) can be decoded and verified per second, per process.

For best performance, extra modules from CPAN should be installed, listed below. Module will use them if they can be loaded.

Module development and TODOs

There is no official roadmap for developing new Bitcoin::Crypto features. The library tries to stay up to date with most impactful additions to the protocol, but no promises can be made.

Contributions are welcome for:

  • All issues with help wanted tag on GitHub

  • Test improvements

  • Documentation improvements

  • Performance improvements

DISCLAIMER

Although the module was written with an extra care and appropriate tests are in place asserting compatibility with many Bitcoin standards, due to complexity of the subject some bugs may still be present. In the world of digital money, a single bug may lead to losing funds. I encourage anyone to test the module themselves, review the test cases and use the module with care. Suggestions for improvements and more edge cases to test will be gladly accepted, but there is no warranty on funds manipulated by this module.

SUPPORT

Use the GitHub bug tracker to report issues. If you have any questions, ask them on GitHub discussions.

SEE ALSO

Bitcoin whitepaper

Bitcoin BIPs

Bitcoin::BIP39

Bitcoin::Secp256k1

App::Bitcoin::PaperWallet

AUTHOR

Bartosz Jarzyna <bbrtj.pro@gmail.com>

Consider supporting my effort: https://bbrtj.eu/support

Contributors

In no particular order:

  • Reginaldo Costa

  • chromatic

COPYRIGHT AND LICENSE

Copyright (C) 2018 - 2025 by Bartosz Jarzyna

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.