SkillAgentSearch skills...

Btcpy

A Python3 SegWit-compliant library which provides tools to handle Bitcoin data structures in a simple fashion.

Install / Use

/learn @chainside/Btcpy
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p> <img src="https://www.chainside.net/wp-content/themes/chainside2018/assets/favicon//favicon-192.png" alt="chainside" width="80"> <br \><br \> developed with :heart: by <a href="https://www.chainside.net">chainside</a> </p>

btcpy

btcpy is a Python>=3.3 SegWit-compliant library which provides tools to handle Bitcoin data structures in a simple fashion. In particular, the main goal of this library is to provide a simple interface to parse and create complex Bitcoin scripts.

N.B.: this library is a work in progress so it is highly discouraged to use it in a production environment. Also, as long as the version is 0.*, API breaking changes should be expected

Table of Contents

Requirements

The strict requirements of this library are:

pip install ecdsa
pip install base58

as an additional requirement, only used for integration testing purposes, this library uses:

pip install python-bitcoinlib==0.7.0

this is used to communicate with the Bitcoin node in order to test transactions validation.

Installation

To install this library and its dependencies one can just run

pip install chainside-btcpy

What it does

The main functionalities provided by this project are the following.

  • Parsing of blocks

  • Parsing, creation and signing of transactions

  • Parsing and creation of scripts. This also includes many nonstandard script types such as:

    • Hashlocked scripts
    • Timelocked scripts, with both absolute and relative times
    • Arbitrarily nested if-else clauses

    all scripts are easily embeddable in P2SH and P2WSH format, also supporting SegWit-over-P2SH formats. This library also offers functions to spend such complex scripts by only providing the necessary data.

What it does not do

This library does not implement the following functionalities:

  • Validation: when blocks, transactions and scripts are parsed, only format errors are reported. No proof-of-work validation, script execution, transaction validation and signature verification is performed. For these consensus-critical functionalities, users of this library should rely on Bitcoin Core or other libraries that perform validation.
  • Communication with the Bitcoin nodes. This is not provided neither on an RPC nor a networking level. For this purpose we highly recommed python-bitcoinlib.

Structure

All important data structures can be found in btcpy.structs, helper modules are located in btcpy.lib. Objects in btcpy.structs are meant as a public interface, while objects located in btcpy.lib are used internally.

Usage examples

Setup

The first thing to do the first time this package is imported is to set a global state which indicates on which network you are working and wether you want strict mode enabled. These two settings are further explained in the following sections.

To setup btcpy, you can use the following function

from btcpy.setup import setup
setup('regtest', strict=True)

Network

You can setup the network you will work on by calling:

from btcpy.setup import setup
setup('regtest')

supported network types are:

regtest
testnet
mainnet

The btcpy.setup module also provides the following network-related functions:

is_mainnet() - returns True if 'mainnet' was selected, False otherwise
net_name()   - returns the value that was selected when calling setup()

Strictness

btcpy never performs validation. However, we don't want you to inadvertently lose your funds for a mistake, so, in strict mode, when you do something that looks dangerous, the library always makes sure that you know exactly what you are doing.

To setup the library in strict mode, you can run the setup as follows:

setup(my_network, strict=True)  # True is actually the default for strict mode, the only other option is False

Additionally, you can force (non-)strictness on specific functions that have a strict=None as keyword argument. If the strict keyword argument is left to None, then the strictness specified in the setup will be followed, otherwise the param you pass to strict will be used.

The following additional checks are done when in strict mode:

  • Do not allow to create P2pkScripts with public keys that have an invalid format (please note that during parsing such scripts will not even be recognised as scripts of type 'p2pk' when strict mode is enabled, they will instead be recognised as of type 'nonstandard')
  • Do not allow to create m-of-n MultisigScripts with less than m public keys that have a valid format (please note that during parsing such scripts will not even be recognised as scripts of type 'multisig' when strict mode is enabled, they will instead be recognised as of type 'nonstandard')
  • Do not allow to decode ExtendedPublicKeys or ExtendedPrivateKeys that don't match the network you set in setup
  • Do not allow to decode Addresses that don't match the network you set in setup

Parsing and serialization

Transaction, PublicKey, PrivateKey and Block can be extracted from a hex string by doing:

from btcpy.structs.transaction import Transaction
from btcpy.structs.block import Block
from btcpy.structs.crypto import PublicKey, PrivateKey
tx = Transaction.unhexlify(hex_tx)
block = Block.unhexlify(hex_block)
pubk = PublicKey.unhexlify(pubk_hex)
privk = PrivateKey.unhexlify(privk_hex)

PublicKey and PrivateKey can also be extracted from their BIP32 formats using the hd module:

>>> from btcpy.structs.hd import ExtendedPrivateKey, ExtendedPublicKey
>>> priv = ExtendedPrivateKey.decode('tprv8kxXxKwakWDtXvKBjjR5oHDFS7Z21HCVLMVUqEFCSVChUZ26BMDDH1JmaGUTEYGMUyQQBSfTgEK76QBvLephodJid5GTEiGFVGJdEBYptd7')
# priv.key holds a `PrivateKey`
>>> priv.key.hexlify()
'a12618ff6540dcd79bf68fda2faf0589b672e18b99a1ebcc32a40a67acdab608'
>>> pub = ExtendedPublicKey.decode('tpubDHea6jyptsuZRPLydP5gCgsN194xAcPPuf6G7kHVrm16K3Grok2oTVvdkNvPM465uuKAShgba7A2hHYeGGuS9B8AQGABfc6hp7mpcLLJUsk')
# pub.key holds a `PublicKey`
>>> pub.key.hexlify()
'025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260'

PrivateKey can also be extracted from a Wallet Import Format by doing:

>>> privk = PrivateKey.form_wif(wif_key)

All these structures can be converted back to hex by using their hexlify() method.

In the same way, these structures can be serialized and deserialized by using their serialize() and deserialize() methods. These methods respectively return and expect a bytearray type.

Keys

The PublicKey class can handle both compressed and uncompressed public keys. In any case both the compressed and uncompressed version can be extracted. However, the structure will remember how it was initialised, so the hexlify(), hash() and to_address() methods will produce different results depending whether the PublicKey was initialised with a compressed or uncompressed public key. The to_segwit_address() method will always consider the key as compressed (P2WPKH addresses are only allowed with compressed keys). An example of this behaviour follows:

>>> uncomp = PublicKey.unhexlify('04ea4e183e8c751a4cc72abb7088cea79351dbfb7981ceb48f286ccfdade4d42c877d334c1a8b34072400f71b2a900a305ffae8963075fe94ea439b4b57978e9e8')
>>> compr = PublicKey(uncomp.compressed)
>>> uncomp.hexlify()
'04ea4e183e8c751a4cc72abb7088cea79351dbfb7981ceb48f286ccfdade4d42c877d334c1a8b34072400f71b2a900a305ffae8963075fe94ea439b4b57978e9e8'
>>> compr.hexlify()
'02ea4e183e8c751a4cc72abb7088cea79351dbfb7981ceb48f286ccfdade4d42c8'
>>> str(uncomp.to_address())
'mtDD9VFhPaRi6C6McMSnhb7nUZceSh4MnK'
>>> str(uncomp.to_segwit_address())
'tb1qxs0gs9dzukv863jud3wpldtrjh9edeqqqzahcz'  # this actually refers to the compressed version!
>>> str(compr.to_address())
'mkGY1QBotzNCrpJaEsje3BpYJsktksi3gJ'
>>> str(compr.to_segwit_address())
'tb1qxs0gs9dzukv863jud3wpldtrjh9edeqqqzahcz'

Please note that by default the to_address() and to_segwit_address() methods will return an address in the format of the network type specified in setup (regtest in the case of this example) but a flag can be passed to it to return an address for another network:

>>> str(uncomp.to_address(mainnet=True))
'1DhFrSAiaYzTK5cjtnUQsfuTca1wXvXfVY'
>>> str(compr.to_address(mainnet=True))
'15kaiM6q5xvx5hpxXJmGDGcDStABoGTzSX'

The PublicKey derived from a PrivateKey can be obtained by doing:

pubk = PrivateKey.unhexlify(privk_hex).pub()

the pub() method will return by default the compressed public key. The uncompressed version can be obtained by adding the flag compressed=False.

Additionall

Related Skills

View on GitHub
GitHub Stars273
CategoryDevelopment
Updated2mo ago
Forks75

Languages

Python

Security Score

100/100

Audited on Jan 22, 2026

No findings