GithubHelp home page GithubHelp logo

chainside / btcpy Goto Github PK

View Code? Open in Web Editor NEW
270.0 26.0 76.0 37.09 MB

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

Home Page: https://www.chainside.net

License: GNU Lesser General Public License v3.0

Python 100.00%
bitcoin transaction p2sh segwit bitcoin-script hacktoberfest

btcpy's Introduction

chainside

developed with ❤️ by chainside

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.

Additionally, one can make sure to use the compressed version of a key by using its compress() method:

>>> compr = uncomp.compress()
>>> str(compr.to_address())
'mkGY1QBotzNCrpJaEsje3BpYJsktksi3gJ'

Addresses can be either created from a PublicKey or from a script. In particular this second use case will be documented in the Addresses section.

HD keys

The structs.hd module provides functionalities to handle BIP32 HD keys. Specifically, it provides the following two classes:

  • ExtendedPublicKey
  • ExtendedPrivateKey

These classes both provide the get_child(index, hardened=False) method. If called on an ExtendedPublicKey, hardened must be set to False, otherwise heardened can be either True or False. The ExtendedPublicKey corresponding to an ExtendedPrivateKey can be obtained through the pub() method.

As seen in the example above, ExtendedPublicKey and ExtendedPrivateKey contain the simpler structures PublicKey and PrivateKey, respectively. These structures can be accessed through the key attribute.

ExtendedPublicKeys also provide a derive() method which takes as input a string representing a path which either starts with 'm' or with '.'. 'm' indicates an absolute path and can be used only when derive() is called on a master key, '.' represents a relative path and can be used from any starting key. Examples of derivation paths:

  • m/0'/1'/2: absolute path, first two derivations hardened
  • ./0/128/256': relative path, last derivation hardened

Scripts

The main focus of this project is providing a simple way to create complex scripts. Scripts have the following hierarchy

  • BaseScript
    • ScriptSig
    • ScriptPubKey
      • P2pkhscript
      • P2wpkhScript
        • P2wpkhV0Script
      • P2shScript
      • P2wshScript
        • P2wshV0Script
      • P2pkScript
      • NulldataScript
      • MultisigScript
      • IfElseScript
      • AbsoluteTimelockScript
      • RelativeTimelockScript
      • Hashlock256Script
      • Hashlock160Script
      • UnknownScript

Scripts have the following methods:

serialize()              - Returns the script as a bytearray
decompile()              - Returns a string representing the human readable opcodes and pushdata operations
hexlify()                - Returns the script as a hex string
unhexlify(hex_string)    - Creates the script from a hex string
is_standard()            - Returns whether the script complies with standardness rules as of Bitcoin Core commit a90e6d2bffc422ddcdb771c53aac0bceb970a2c4
type                     - A property containing a string which represents the type of the script
get_sigop_count()        - Returns the number of signature operations performed by the script
is_push_only()           - Returns whether the script is only made of push operations
to_address(segwit=False) - (only ScriptPubKey) Returns the script as either a P2SH or a P2WSH address, depending whether
                           the segwit flag is set

Low-level scripting functionalities

This section will introduce low-level creation and template-matching of scripts, for more advanced features please refer to the Transactions section.

This libary allows to create scripts from asm and from hex, as can be seen in the following examples.

Creating a script from asm (i.e. opcodes):

# this returns a bytearray with the compiled script
>>> compiled = Script.compile('OP_DUP OP_HASH160 a33ce8cf2760e2f9ef384bcbbe9a5491759feb14 OP_EQUALVERIFY OP_CHECKSIG')
# the bytearray can be passed to Script() to get a generic script
>>> script = Script(compiled)
# check that everything works as expected
>>> script.decompile()
'OP_DUP OP_HASH160 a33ce8cf2760e2f9ef384bcbbe9a5491759feb14 OP_EQUALVERIFY OP_CHECKSIG'
# beware, this is a generic script, no type recognition has been performed!
>>> script.type
'Script'

Creating a script from hex:

# this returns a bytearray with the compiled script
>>> script = Script.unhexlify('76a914a33ce8cf2760e2f9ef384bcbbe9a5491759feb1488ac')
# check that everything works as expected
>>> script.decompile()
'OP_DUP OP_HASH160 a33ce8cf2760e2f9ef384bcbbe9a5491759feb14 OP_EQUALVERIFY OP_CHECKSIG'
# beware, this is a generic script, no type recognition has been performed!
>>> script.type
'Script'

As we have seen, these are instantiated as generic scripts, if we want to obtain the appropriate script type, the ScriptBuilder class can be used. ScriptBuilder's method identify() will return the appropriate script type by performing template matching on the provided script.

Identifying a P2PKH script:

>>> script = ScriptBuilder.identify('76a914341e8815a2e5987d465c6c5c1fb56395cb96e40088ac')
>>> script.type
'p2pkh'
>>> script.decompile()
'OP_DUP OP_HASH160 341e8815a2e5987d465c6c5c1fb56395cb96e400 OP_EQUALVERIFY OP_CHECKSIG'
>>> script.pubkeyhash
bytearray(b'4\x1e\x88\x15\xa2\xe5\x98}F\\l\\\x1f\xb5c\x95\xcb\x96\xe4\x00')

Identifying a P2SH script

>>> script = ScriptBuilder.identify('a914bb18ed39c2a86f75f7bb5a9b36ba3581d77fd0f087')
>>> script.type
'p2sh'
>>> script.decompile()
'OP_HASH160 bb18ed39c2a86f75f7bb5a9b36ba3581d77fd0f0 OP_EQUAL'
>>> script.scripthash
bytearray(b'\xbb\x18\xed9\xc2\xa8ou\xf7\xbbZ\x9b6\xba5\x81\xd7\x7f\xd0\xf0')

Of course, all the types listed at the beginning of this section can be recognised, see the next section for more complex script types.

Please keep in mind that the fact that a script is successfully built (a script can be built for every recognised script type, if no type matches, an UnknownScript is istantiated) does not mean that the script is valid. In fact, UnknownScripts can even contain non valid push operations or non-existing opcodes. The only way to know if a script is valid is executing it against an execution stack, a functionality that this library does not implement. In particular, for non-valid push operations, the script asm (obtained through the decompile or __str__ methods) will contain [error] where the push takes place. For non-existing opcodes the asm will contain the special opcode OP_INVALIDOPCODE. These two beahviours match Bitcoin Core's behaviour when producing script asm.

Addresses

Supported addresses are: P2pkhAddress, P2shAddress, P2wpkhAddress and P2wshAddress. These constructors can be used to build an address from a hash (plus a SegWit version in the case of P2wpkhAddress or P2wshAddress), for example:

from btcpy.structs.crypto import PublicKey
from btcpy.structs.address import P2pkhAddress, P2wpkhAddress
pubk = PublicKey.unhexlify('02ea4e183e8c751a4cc72abb7088cea79351dbfb7981ceb48f286ccfdade4d42c8')
address = P2pkhAddress(pubk.hash())
sw_address = P2wpkhAddress(pubk.hash(), version=0)
print(str(address))  # prints "mkGY1QBotzNCrpJaEsje3BpYJsktksi3gJ"
print(str(sw_address))  # prints "tb1qxs0gs9dzukv863jud3wpldtrjh9edeqqqzahcz"

Please note that by default all the address constructors will return an address in the format of the network type specified in setup (testnet in the case of this example) but a flag can be passed to them to return an address for another network:

address = P2pkhAddress(pubk.hash(), mainnet=True)
sw_address = P2wpkhAddress(pubk.hash(), version=0, mainnet=True)
print(str(address))  # prints "15kaiM6q5xvx5hpxXJmGDGcDStABoGTzSX"
print(str(sw_address))  # prints "bc1qxs0gs9dzukv863jud3wpldtrjh9edeqq2yxyr3"

However, a more common usecase is generating an address for a script, for this the from_script static method of all address classes can be used, in particular:

  • P2pkhAddress.from_script(script, mainnet=None) will instantiate a P2pkhAddress from a P2pkhScript, raising WrongScriptType exception in case another type of script is provided.
  • P2shAddress.from_script(script, mainnet=None) will instantiate a P2shAddress representing the script address if a P2shscript is provided, while returning the address of the script embedded in P2SH format if other script types are provided.
  • P2wpkhAddress.from_script(script, version, mainnet=None) will instantiate a P2wpkhAddress from a P2wpkhScript, raising WrongScriptType exception in case another type of script is provided.
  • P2wshAddress.from_script(script, version, mainnet=None) will instantiate a P2wshAddress representing the script address if a P2wshscript is provided, while returning the address of the script embedded in P2WSH format if other script types are provided.

The only scripts that directly support an address (i.e. P2pkhScript, P2wpkhScript, P2shscript, P2wshScript) also provide a helper method address() to return the script address, for all other script types will return None if the address() method is called and will need to be explicitly converted to P2SH or P2WSH format to obtain an address. Some examples follow:

>>> str(P2pkhAddress.from_script(P2pkhScript(pubk)))
'mkGY1QBotzNCrpJaEsje3BpYJsktksi3gJ'
>>> str(P2pkhScript(pubk).address())
'mkGY1QBotzNCrpJaEsje3BpYJsktksi3gJ'
>>> str(P2pkhAddress.from_script(P2shScript(P2pkhScript(pubk))))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../btcpy/btcpy/structs/address.py", line 120, in from_script
    raise WrongScriptType('Trying to produce P2pkhAddress from {} script'.format(script.__class__.__name__))
btcpy.structs.address.WrongScriptType: Trying to produce P2pkhAddress from P2shScript script
>>> str(P2shAddress.from_script(P2shScript(P2pkhScript(pubk))))
'2NAJWD6EnXMVt16HUp5vmfwPjz4FemvPhYt'
>>> str(P2shScript(P2pkhScript(pubk)).address())
'2NAJWD6EnXMVt16HUp5vmfwPjz4FemvPhYt'
>>> str(P2wpkhAddress.from_script(P2wpkhV0Script(pubk)))
'tb1qxs0gs9dzukv863jud3wpldtrjh9edeqqqzahcz'
>>> str(P2wpkhV0Script(pubk).address())
'tb1qxs0gs9dzukv863jud3wpldtrjh9edeqqqzahcz'
>>> str(P2wpkhAddress.from_script(P2shScript(P2wpkhV0Script(pubk))))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../btcpy/btcpy/structs/address.py", line 158, in from_script
    raise WrongScriptType('Trying to produce P2pkhAddress from {} script'.format(script.__class__.__name__))
btcpy.structs.address.WrongScriptType: Trying to produce P2pkhAddress from P2shScript script

On the other hand, addresses can also be directly converted to the scripts they represent:

>>> a = Address.from_string('mkGY1QBotzNCrpJaEsje3BpYJsktksi3gJ')
>>> a.to_script()
P2pkhScript('341e8815a2e5987d465c6c5c1fb56395cb96e400')
>>> a = Address.from_string('tb1qxs0gs9dzukv863jud3wpldtrjh9edeqqqzahcz')
>>> a.to_script()
P2wpkhScript('341e8815a2e5987d465c6c5c1fb56395cb96e400')

Transactions

Creating transactions

Transactions can be created by using the following classes:

  • TxIn, takes as input the following parameters:
    • txid, the txid of the transaction being spent
    • txout, the output number of the output being spent
    • script_sig, a scriptSig
    • sequence, the sequence number of the TxIn
  • Sequence, the constructor takes a sequence number, but it offers a couple of helper static methods for creation:
    • create(), which takes seq, lower 16 bits of sequence number, blocks, whether the seq param expresses blocks or a timestamp, and disable which sets the disable bit. For further info on how this all works, please refer to BIP68 specification.
    • max(), this automatically creates a Sequence object with the maximum sequence number (i.e. 0xffffffff).
  • TimebasedSequence, behaves like a Sequence but assumes the sequence expresses time. Can also be instantiated from a timedelta object through its from_timedelta method
  • HeightBasedSequence, behaves like a Sequence but assumes the sequence expresses a block height.
  • ScriptSig, this can be initialised with a bytearray representing the script, but offers the following static methods:
    • empty(), this creates an empty ScriptSig, useful when initialising a transaction which has not been signed yet
  • StackData, this class represents data that scripts push on the stack, it offers methods to convert between the push operations and the actual data pushed.
  • Witness, this represents a SegWit witness, it is constructed with an array of StackData.
  • TxOut, takes as input the following parameters: value the value spent, in satoshis, n, the output number, script_pubkey, an object of type ScriptPubKey where the coins are being sent.
  • ScriptPubKey and derived classes, they take as input a bytearray representing the script but can also be created through the ScriptBuilder.identify() method or in the way displayed later in this section.
  • Locktime, takes as input a number representing the transaction's locktime field. Can also be constructed from a datetime object through its from_datetime method
  • Transaction, takes as inputs: a version number, a list of TxIns, a list of TxOuts, a Locktime.
  • SegWitTransaction, has the same interface as Transaction
  • TransactionFactory used to instantiate a generic transaction from a json or hex string

All the aforementioned classes are Immutable, this means that, after construction, their attributes can't be mutated. This helps caching values returned by their methods. The classes Transaction, SegWitTransaction and TxIn have mutable versions, unsurprisingly called MutableTransaction, MutableSegWitTransaction and MutbleTxIn, respectively. These mutable versions are mainly used to create unsigned transactions which then are mutated to add signatures to them. We will see how to use these in the rest of this section.

Transactions can be deserialized both from json and from a hex string, see the following examples:

>>> tx = Transaction.unhexlify('0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000')
>>> tx.to_json()
{'hex': '0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000', 'txid': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'hash': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'size': 192, 'vsize': 192, 'version': 1, 'locktime': 0, 'vin': [{'txid': '05e69c373f787ab7635465db94225307e4ad6685d3df63ff605efebe3f17dae4', 'vout': 0, 'scriptSig': {'asm': '3045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f101 02ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2', 'hex': '483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2'}, 'sequence': '4294967295'}], 'vout': [{'value': '2.00000000', 'n': 0, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 df76c017354ac39bde796abe4294d31de8b5788a OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a914df76c017354ac39bde796abe4294d31de8b5788a88ac', 'type': 'p2pkh', 'address': '1MNZwhTBHN3QTXkwob7NvhVaTVKUm7MRCg'}}]}
>>> tx = SegWitTransaction.unhexlify('0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/rael/Dropbox/projects/btcpy/btcpy/structs/transaction.py", line 457, in unhexlify
    return cls.deserialize(bytearray(unhexlify(string)))
  File "/home/rael/Dropbox/projects/btcpy/btcpy/structs/transaction.py", line 466, in deserialize
    raise TypeError('Trying to load transaction from wrong transaction serialization')
TypeError: Trying to load transaction from wrong transaction serialization
>>> tx = Transaction.from_json({'hex': '0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000', 'txid': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'hash': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'size': 192, 'vsize': 192, 'version': 1, 'locktime': 0, 'vin': [{'txid': '05e69c373f787ab7635465db94225307e4ad6685d3df63ff605efebe3f17dae4', 'vout': 0, 'scriptSig': {'asm': '3045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f101 02ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2', 'hex': '483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2'}, 'sequence': '4294967295'}], 'vout': [{'value': '2.00000000', 'n': 0, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 df76c017354ac39bde796abe4294d31de8b5788a OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a914df76c017354ac39bde796abe4294d31de8b5788a88ac', 'type': 'p2pkh', 'address': '1MNZwhTBHN3QTXkwob7NvhVaTVKUm7MRCg'}}]})
>>> tx = SegWitTransaction.from_json({'hex': '0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000', 'txid': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'hash': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'size': 192, 'vsize': 192, 'version': 1, 'locktime': 0, 'vin': [{'txid': '05e69c373f787ab7635465db94225307e4ad6685d3df63ff605efebe3f17dae4', 'vout': 0, 'scriptSig': {'asm': '3045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f101 02ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2', 'hex': '483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2'}, 'sequence': '4294967295'}], 'vout': [{'value': '2.00000000', 'n': 0, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 df76c017354ac39bde796abe4294d31de8b5788a OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a914df76c017354ac39bde796abe4294d31de8b5788a88ac', 'type': 'p2pkh', 'address': '1MNZwhTBHN3QTXkwob7NvhVaTVKUm7MRCg'}}]})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/rael/Dropbox/projects/btcpy/btcpy/structs/transaction.py", line 737, in from_json
    raise TypeError('Trying to load segwit transaction from non-segwit transaction json')
TypeError: Trying to load segwit transaction from non-segwit transaction json

As you can see from the previous example, Transaction and SegWitTransaction classes can deserialise only json and hex strings of the appropriate type. To deserialize a generic json or hex string and build the appropriate object, one can use the TransactionFactory:

>>> TransactionFactory.unhexlify('0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000')
<btcpy.structs.transaction.Transaction object at 0x7f3717961be0>
>>> TransactionFactory.from_json({'hex': '0100000001e4da173fbefe5e60ff63dfd38566ade407532294db655463b77a783f379ce605000000006b483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2ffffffff0100c2eb0b000000001976a914df76c017354ac39bde796abe4294d31de8b5788a88ac00000000', 'txid': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'hash': 'e977c07090c2a1dcaefd3f3c4ebf4e231f4116cb272f805b0b22a85e7eece09c', 'size': 192, 'vsize': 192, 'version': 1, 'locktime': 0, 'vin': [{'txid': '05e69c373f787ab7635465db94225307e4ad6685d3df63ff605efebe3f17dae4', 'vout': 0, 'scriptSig': {'asm': '3045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f101 02ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2', 'hex': '483045022100af246c27890c2bc07a0b7450d3d82509702a44a4defdff766355240b114ee2ac02207bb67b468452fa1b325dd5583879f5c1412e0bb4dae1c2c96c7a408796ab76f1012102ab9e8575536a1e99604a158fc60fe2ebd1cb1839e919b4ca42b8d050cfad71b2'}, 'sequence': '4294967295'}], 'vout': [{'value': '2.00000000', 'n': 0, 'scriptPubKey': {'asm': 'OP_DUP OP_HASH160 df76c017354ac39bde796abe4294d31de8b5788a OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a914df76c017354ac39bde796abe4294d31de8b5788a88ac', 'type': 'p2pkh', 'address': '1MNZwhTBHN3QTXkwob7NvhVaTVKUm7MRCg'}}]})
<btcpy.structs.transaction.Transaction object at 0x7f3717971518>

Example of a transaction creation:

>>> from btcpy.structs.transaction import Transaction, TxIn, Sequence, TxOut, Locktime
>>> script_sig = Script.unhexlify('48304502210083e6e7507e838a190f0443441c0b62d2df94673887f4482e27e89ff415a90392022050575339c649b85c04bb410a00b62325c1b82c537135fa62fb34fae2c9a30b0b01210384478d41e71dc6c3f9edde0f928a47d1b724c05984ebfb4e7d0422e80abe95ff')
>>> script_pubkey = ScriptBuilder.identify('76a914905f77004d081f20dd421ba5288766d56724c3b288ac')
>>> tx = Transaction(version=1,
...                  ins=[TxIn(txid='1a5a4f9a0d34cfca187db4fe6a3316f46264984c4b4c9fdb582123815afd508f',
...                            txout=0,
...                            script_sig=script_sig,
...                            sequence=Sequence.max())],
...                  outs=[TxOut(value=193000000,
...                              n=0,
...                              script_pubkey=script_pubkey)],
...                  locktime=Locktime(0))
>>> tx.txid
'14e6afbae7d2b1825b7ee711cbcad77d519767b70f5a1e70e5ba7f0bfc902e81'

Example creation of a SegWit transaction:

>>> from btcpy.structs.transaction import SegWitTransaction, Witness
>>> from btcpy.structs.script import StackData, empty_script
>>> witness_sig = StackData.from_bytes(unhexlify('304402200d0fbf48270e690be17cb0c47ee6ce2df3b671c2e4b196065e09c6df649b807c022056d8f10da83b2856458152c7f09e53a3495f3fbdd2e20638586a52ddff4f495b01'))
>>> witness_pubkey = StackData.from_bytes(unhexlify('02a079cb0269c933b1ee041a933092c9c439dd1b3a4eebd32ae391cf815002d378'))
>>> witness = Witness([witness_sig, witness_pubkey])
>>> script_pubkey = ScriptBuilder.identify('a914b2eb061810dac0614ac3e06d1bc55077b32b3b2687')
>>> tx = SegWitTransaction(version=1,
...                        ins=[TxIn(txid='1a5a4f9a0d34cfca187db4fe6a3316f46264984c4b4c9fdb582123815afd508f',
...                                  txout=0,
...                                  script_sig=empty_script,
...                                  sequence=Sequence.max(),
...                                  witness=witness],
...                        outs=[TxOut(value=193000000,
...                                    n=0,
...                                    script_pubkey=script_pubkey)],
...                        locktime=Locktime(0))
>>> tx.txid
'14dd31532ca06d62121fd13d35a2c9090246291960e73bf2bb3615abcb1bedab'

Of course, nobody would like to create transactions in such a cumbersome way. In fact, this library provides the appropriate tools to create complex scriptPubKeys in an easy fashion and to automatically fill in scriptSigs and witnesses of a spending transaction based on the minimum needed parameters. In the following sections we will show some examples of these features.

The supported scripts can be created by using their constructor and passing them the needed parameters. They can be found in btcpy.structs.script. All the constructors of these classes can take an input of type Script. In this case they try to match it to their template and raise a WrongScriptTypeException if the script does not match the desired template. Otherwise, they take the following parameters:

Class Description Parameters
P2pkhScript, P2wpkhScript A P2PKH/P2WPKH script Either a PublickKey, a bytearray representing a public key hash or an Address
P2shScript A P2SH script Either a ScriptPubKey representing the redeemScript, a bytearray representing the redeemScript's hash or an Address
P2wshScript A P2WSH script Either a ScriptPubKey representing the witnessScript, a bytearray representing the witnessScript's hash or an Address
P2pkScript A P2PK script A PublicKey
NulldataScript An OP_RETURN script A StackData representing the data to store in the transaction
MultisigScript A multisig script, where m out of n keys are needed to spend m, the number of signatures needed to spend this output, an arbitrary number of PublicKeys, n the number of public keys provided
IfElseScript A script consisting of an OP_IF, a script, an OP_ELSE, another script and an OP_ENDIF Two ScriptPubKey scripts, the first to be executed in the if branch, the second to be executed in the else branch
AbsoluteTimelockScript A script consisting of <pushdata> OP_CHECKLOCKTIMEVERIFY OP_DROP and a subsequent script which can be spent only after the absolute time expressed by the <pushdata> is expired A Locktime, expressing the absolute time/number of blocks after which the subsequent script can be spent, and the locked ScriptPubKey
RelativeTimelockScript A script consisting of <pushdata> OP_CHECKSEQUENCEVERIFY OP_DROP and a subsequent script which can be spent only after the relative time time expressed by the <pushdata> is expired A Sequence, expressing the relative time/ number of blocks after which the subsequent script can be spent, and the locked ScriptPubKey
Hashlock256Script A script consisting of OP_HASH256 <pushdata> OP_EQUALVERIFY and a subsequent script which can be spent only after providing the preimage of <pushdata> for the double SHA256 hash function Either a bytearray or StackData representing the hashed value that locks the subsequent script, plus the locked ScriptPubKey
Hashlock160Script A script consisting of OP_HASH160 <pushdata> OP_EQUALVERIFY and a subsequent script which can be spent only after providing the preimage of <pushdata> for the RIPEMPD160 of the SHA256 hash function Either a bytearray or StackData representing the hashed value that locks the subsequent script, plus the locked ScriptPubKey

Please note that in the following sections we will frequently use the same keypair for ease of documenting, of course this is a very bad practice in a production environment and should be avoided at all costs.

Spending a transaction

This library offers Solvers to spend a previous transaction's output. Solvers can be found in btcpy.structs.sig and expect as input all the data needed to create the appropriate scriptSig and witness. To create a Solver, the Sighash class is needed. This class represents a SIGHASH and its constructor takes two parameters:

  • sighash, either of the literal strings 'ONE', 'ALL' or 'NONE'
  • anyonecanpay, a flag defaulting to False.

The following solvers take one sighash as last parameter, defaulting to Sighash('ALL'):

  • P2pkhSolver
  • P2wpkhV0Solver
  • P2pkSolver

The MultisigSolver class takes many sighashes as additional last parameters, all defaulting to Sighash('ALL'). All other classes do not accept sighashes.

Additionally, the following solvers are available and they take the following inputs:

Class Inputs Solves
P2pkhSolver a PrivateKey P2pkhScript
P2wpkhV0Solver a PrivateKey P2wpkhV0Script
P2pkSolver a PrivateKey P2pkhScript
P2shSolver a ScriptPubKey, representing the redeemScript and a Solver which solves the redeemScript P2shScript
P2wshV0Solver a ScriptPubKey, representing the witnessScript and a Solver which solves the inner witnessScript P2wshV0Script
MultisigSolver an arbitrary number of PrivateKeys MultisigScript
IfElseSolver an object of type Branch. This is an enum and its values are Branch.IF and Branch.ELSE, these are used to specify whether we are spending the if or else branch of the script. The second parameter is a Solver for the script inside the desired branch. IfElseScript
TimelockSolver a Solver of the inner timelocked script AbsoluteTimelockScript, RelativeTimelockScript
RelativeTimelockSolver a Solver of the inner timelocked script, with relative timelocks RelativeTimelockScript
AbsoluteTimelockSolver a Solver of the inner timelocked script, with absolute timelocks AbsoluteTimelockScript
HashlockSolver the preimage needed to spend the script, as a bytearray, and a Solver for the hashlocked script Hashlock256Script, Hashlock160Script

To spend a previous transaction, the MutableTransaction class provides the spend() method. The spend() method expects the following inputs:

  • txouts, an array of TxOuts being spent by the transaction's inputs, in the correct order.
  • solvers, an array of Solvers, one per input, in the correct order

for example:

>>> from btcpy.structs.sig import *
>>> to_spend = Transaction.unhexlify('...')
>>> unsigned = MutableTransction(version=1,
...                              ins=[TxIn(txid=to_spend.txid,
...                                        txout=0,
...                                        script_sig=ScriptSig.empty(),
...                                        sequence=Sequence.max())],
...                              outs=[TxOut(value=100000,
...                                          n=0,
...                                          script_pubkey=P2pkhScript(pubk))],
...                              locktime=Locktime(0))
>>> solver = P2pkhSolver(privk)
>>> signed = unsigned.spend([to_spend.outs[0]], [solver])

In particular, the spend() method automatically recognises whether we are spending a SegWit transaction, hence returning either a Transaction or a SegWitTransaction.

Now, let's see how more complex scripts can be created and spent. In the following examples, in solvers, we will always use the default SIGHASH_ALL, to change this, as described above, one can use the last parameter of the solvers that accept SIGHASHes.

P2PKH

This is how a P2PKH script can be created:

# create public key
>>> pubk = PublicKey.unhexlify('025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260')
# create P2PKH script
>>> p2pkh_script = P2pkhScript(pubk)
>>> p2pkh_script.hexlify()
'76a914905f77004d081f20dd421ba5288766d56724c3b288ac'
>>> str(p2pkh_script)
'OP_DUP OP_HASH160 905f77004d081f20dd421ba5288766d56724c3b2 OP_EQUALVERIFY OP_CHECKSIG'

and this is an example of a P2PKH solver:

>>> privk = PrivateKey.unhexlify('a12618ff6540dcd79bf68fda2faf0589b672e18b99a1ebcc32a40a67acdab608')
>>> p2pkh_solver = P2pkhSolver(privk)

now let's assume we have an unsigned mutable transaction, we will use this solver to fill in the transaction's scriptSig:

>>> unsigned_tx = MutableTransaction(...)
>>> previous_txout = TxOut(value=1000, n=0, script_pubkey=p2pkh_script)
>>> signed_tx = unsigned_tx.spend([previous_txout], [p2pkh_solver])

P2SH

Creating a P2SH script that embeds a P2PKH script:

>>> p2sh_script = P2shScript(P2pkhScript(pubk))
>>> p2sh_script.hexlify()
'a914cd1ab43e7c01a08886fd0e699988d2f44c9c57cc87'
>>> str(p2sh_script)
'OP_HASH160 cd1ab43e7c01a08886fd0e699988d2f44c9c57cc OP_EQUAL'

A solver to spend it would be:

>>> privk = PrivateKey.unhexlify('a12618ff6540dcd79bf68fda2faf0589b672e18b99a1ebcc32a40a67acdab608')
>>> solver = P2shSolver(P2pkhScript(pubk),  # the redeemScript
                        P2pkhSolver(privk)) # the redeemScript's solver

P2WSH

Creating a P2WSH script that embeds a P2PKH script:

>>> p2wsh_script = P2wshV0Script(P2pkhScript(pubk))
>>> p2wsh_script.hexlify()
'002058f04cd072784e9dede6821772a195cef65424f2e4957e14232e642bbbdf1aec'
>>> str(p2wsh_script)
'OP_0 58f04cd072784e9dede6821772a195cef65424f2e4957e14232e642bbbdf1aec'

Solving it:

>>> solver = P2wshV0Solver(P2pkhScript(pubk),  # witness script
...                        P2pkhSolver(privk)) # witness script's solver

P2WSH-over-P2SH

Let's now create a P2SH scriptPubKey that embeds a P2WSH that, in turn, embeds a P2PKH:

>>> p2wsh_over_p2sh = P2shScript(P2wshV0Script(P2pkhScript(pubk)))
>>> p2wsh_over_p2sh.hexlify()
'a914efbd1b969b0e15e7a3dc9b1128e4cf493974e62187'
>>> str(p2wsh_over_p2sh)
'OP_HASH160 efbd1b969b0e15e7a3dc9b1128e4cf493974e621 OP_EQUAL'
>>> solver = P2shSolver(
...              P2wshV0Script(P2pkhScript(pubk)),   # redeemScript
...              P2wshV0Solver(            # redeemScript solver
...                  P2pkhScript(pubk),    # witnessScript
...                  P2pkhSolver(privk)    # witnessScript solver
...              )
...           )

P2PK

>>> p2pk_script = P2pkScript(pubk)
>>> p2pk_script.hexlify()
'21025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260ac'
>>> str(p2pk_script)
'025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260 OP_CHECKSIG'
>>> solver = P2pkSolver(privk)

Multisig

>>> privk2 = PrivateKey.unhexlify('710b464f020b676fd9ec3af28d014dec9c8582e6a9059731a3e14aa762527ae4')
>>> pubk2 = privk2.pub()
>>> multisig_script = MultisigScript(1, pubk, pubk2, 2)  # a 1-of-2 multisig
>>> multisig_script.hexlify()
'5121025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe882602102a5f22a78db5c38eaa18f73390e82e000bd52ab84edbcb3ad9b4124460acaf5ee52ae'
>>> str(multisig_script)
'OP_1 025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260 02a5f22a78db5c38eaa18f73390e82e000bd52ab84edbcb3ad9b4124460acaf5ee OP_2 OP_CHECKMULTISIG'
>>> multisig_solver = MultisigSolver(privk)  # this could potentially be passed a list of SIGHASHES in the end to use them when signing

As one will usually embed this in a P2SH format, this could be done as follows:

>>> p2sh_multisig = P2shScript(multisig_script)
>>> solver = P2shSolver(multisig_script, multisig_solver)

Timelocks, Hashlocks, IfElse

Now we are going to create a very complex output. This output can be spent in two ways:

  1. at any time, with two out of two signatures
  2. 5 blocks after it has entered a block, with only one signature. This script is hence composed of two possible execution flows: an if branch and an else branch. Inside the first branch, a 2-of-2 multisig script can be found. Inside the second branch there is a timelocked script. Such a script has a time (a relative time in this case, expressed as a Sequence number) and an inner script, which is the one that can be executed after the relative time has expired. We can create such a script in the following way:
>>> timelocked_multisig = IfElseScript(
...     # if branch
...     MultisigScript(  # a multisig script, as above
...         2,
...         pubk,
...         pubk2,
...         2
...     ),
...     # else branch
...     RelativeTimelockScript(  # timelocked script
...         Sequence(5),  # expiration, 5 blocks
...         P2pkhScript(  # locked script
...             pubk
...         )
...     )
... )

Let's see this script a bit more in depth:

>>> timelocked_multisig.type
'if{ multisig }else{ [relativetimelock] p2pkh }'
>>> str(timelocked_multisig)
'OP_IF OP_2 025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260 02a5f22a78db5c38eaa18f73390e82e000bd52ab84edbcb3ad9b4124460acaf5ee OP_2 OP_CHECKMULTISIG OP_ELSE OP_5 OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 905f77004d081f20dd421ba5288766d56724c3b2 OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF'
>>> timelocked_multisig.if_script
MultisigScript(2, 025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260, 02a5f22a78db5c38eaa18f73390e82e000bd52ab84edbcb3ad9b4124460acaf5ee, 2)
>>> str(timelocked_multisig.if_script)
'OP_2 025f628d7a11ace2a6379119a778240cb70d6e720750416bb36f824514fbe88260 02a5f22a78db5c38eaa18f73390e82e000bd52ab84edbcb3ad9b4124460acaf5ee OP_2 OP_CHECKMULTISIG'
>>> timelocked_multisig.else_script
RelativeTimelockScript(5, OP_DUP OP_HASH160 905f77004d081f20dd421ba5288766d56724c3b2 OP_EQUALVERIFY OP_CHECKSIG)
>>> str(timelocked_multisig.else_script)
'OP_5 OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 905f77004d081f20dd421ba5288766d56724c3b2 OP_EQUALVERIFY OP_CHECKSIG'
>>> timelocked_multisig.else_script.locked_script
P2pkh(905f77004d081f20dd421ba5288766d56724c3b2)
>>> timelocked_multisig.else_script.locked_script.decompile()
'OP_DUP OP_HASH160 905f77004d081f20dd421ba5288766d56724c3b2 OP_EQUALVERIFY OP_CHECKSIG'

Let's write the solvers for this script:

>>> solver_if = IfElseSolver(Branch.IF,                      # branch selection
...                          MultisigSolver(privk, privk2))  # inner solver
>>> solver_else = IfElseSolver(Branch.ELSE,
...                            RelativeTimelockSolver(Sequence(5), P2pkhSolver(privk)))

Low-level signing

If one wants to sign a transaction by hand, instead of using solvers, one of the following procedures can be used:

  • Manually writing the scriptSig (this can be seen in the Creating transactions section)
  • Creating the scriptSig by computing and signing the digest of the transaction

Let's see an example of this last case:

>>> unsigned = MutableTransaction(...)
>>> digest = unsigned.get_digest(2,   # the input to be signed
                                 prev_script,  # the previous script to spend (this is the redeem/witness script in case of P2SH/P2WSH ouputs)
                                 sighash=Sighash('NONE', anyonecanpay=True))  # sighash: 0x02 | 0x80
>>> privk.sign(digest)

In case one wants to sign a SegWit digest for the transaction, the following can be done:

>>> unsigned = SegWitTransaction(...)
>>> digest = unsigned.get_segwit_digest(2,   # the input to be signed
                                        prev_script,  # the previous script to spend (this is the redeem/witness script in case of P2SH/P2WSH ouputs)
                                        prev_amount,  # the amount of the output being spent
                                        sighash=Sighash('NONE', anyonecanpay=True))  # sighash: 0x02 | 0x80
>>> privk.sign(digest)

Contributing and running tests

This library has two testing tools that can be found in the tests/ folder:

  • unit.py, this runs basic unit testing
  • integration.py, this runs tests of signed transactions, to do this, transactions are signed and sent to a Bitcoin Core node through the sendrawtransaction command.

To make sure these tests are using the code in the current repository and not a stale copy installed in a virtualenv or system wide, please make sure to run the following commands from the root of the repo:

python3 -m unittest tests/unit.py
python3 -m unittest tests/integration.py

Contributors are invited to run these tests before submitting PRs. Also, contributions to improve and expand these tests are highly welcome.

Roadmap to v1

This library's stable version 1 will be released once the following changes are made:

  • More efficient script matching (i.e. scripts should be able to specify fast matching conditions instead of trying to parse the raw bytes to decide whether the template is matched)
  • Caching on SegWit digest computation to avoid quadratic hashing
  • Generation of private keys through secure entropy sources
  • An extensive documentation of all modules, classes and their parameters is produced

TODO

Since this library is still a work in progress, the following roadmap lists the improvements to be done eventually:

  • Expanding the test suites
  • Adding docstrings where missing (many places)
  • Handling OP_CODESEPARATORs in the signing process
  • Add further transaction creation helpers
  • Add RPC calls to Bitcoin Core nodes
  • Add networking with Bitcoin Core nodes

Acknowledgements

Special thanks to gdecicco and lorenzogiust for contributing with performance improvements and general review.

btcpy's People

Contributors

fedsten avatar gdecicco avatar jooray avatar l0rb avatar lorenzogiust avatar peerchemist avatar simonebronzini avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

btcpy's Issues

Different address from which I need

Here is the address which I need 2N2qWuN9rpfw4DvPqjJ3SD1adNnmZdeM5NU (testnet)

Here is what I do to get it:

def p2sh_address(pub_key_1, pub_key_2):
    pub_key_hash_1 = pub_key_1.hash()
    pub_key_hash_2 = pub_key_2.hash()

    multisig_script = MultisigScript(2, pub_key_1, pub_key_2, 2)
    return (multisig_script, P2shAddress.from_script(multisig_script))

Here is the redeem script hex which I got:
5221020d2036fea368d0400acabe96e975b6f4e24c6f40f2e45db4264c2f117702c1e72102d0b84f4a3d4890b2fdde4dc74db4328fb3504e0247187d8286311c75b3b6bdeb52ae
It's the same as on the block.io

Here is the address which I got from from_script:
2N7tpkpBJPmv2BD37eTXDUJJzhpXEoyyNU9

Am I doing something wrong or it should be like that?

Error: StopIteration: Trying to shift empty string

block 260066

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/btcpy/structs/block.py", line 35, in unhexlify
    return Block.deserialize(bytearray(unhexlify(string)))
  File "/usr/local/lib/python3.6/site-packages/btcpy/structs/block.py", line 41, in deserialize
    txns = parser.get_txns()
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 131, in get_txns
    txns.append(txns_parser.get_next_tx())
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 208, in get_next_tx
    txouts = self._txouts()
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 185, in _txouts
    return [self._txout(i) for i in range(self.parse_varint())]
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 185, in <listcomp>
    return [self._txout(i) for i in range(self.parse_varint())]
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 181, in _txout
    script = ScriptBuilder.identify(self >> self.parse_varint())
  File "/usr/local/lib/python3.6/site-packages/btcpy/structs/script.py", line 1231, in identify
    candidate = script_type(Script(raw_script))
  File "/usr/local/lib/python3.6/site-packages/btcpy/structs/script.py", line 800, in __init__
    object.__setattr__(self, 'data', self.verify(param.body))
  File "/usr/local/lib/python3.6/site-packages/btcpy/structs/script.py", line 519, in verify
    args = [data for data in parser.match(cls.template)]
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 257, in match
    pushes.append(self.require_push(op[1:-1]))
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 291, in require_push
    push_data = self.get_push()
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 315, in get_push
    curr_op = next(self)
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 64, in __next__
    return (self >> 1)[0]
  File "/usr/local/lib/python3.6/site-packages/btcpy/lib/parsing.py", line 49, in __rshift__
    raise StopIteration('Trying to shift empty string')
StopIteration: Trying to shift empty string

from_point

The PubKey class has a method from_point that accepts a parameter called point. Can you explain with an example how I can use this function , if I have a pair of decimal numbers that from the EC corresponding to a public key ?

Leading zeros in ExtendedPrivateKey break PrivateKey

When creating a PrivateKey from the output of serialize_key() on ExtendedPrivateKey the leading zeros that this data has break the pub() function on the PrivateKey. Code example below:

rootkey = 'tprv8....'

# this throws an error
PrivateKey(
    ExtendedPrivateKey.decode(rootkey).serialize_key()
).pub()

# this one works
PrivateKey(
    ExtendedPrivateKey.decode(rootkey).key.serialize()
).pub()

SegWit digest caching

SegWit digest computation (btcpy.structs.transaction.SegWitTransaction.get_segwit_digest() method) should cache past computed values and reuse them all around, hence avoiding quadratic hashing.

to_json for block.py

The to_json for block serializes the txns to set not list, so json.dumps wont serialize that

Addresses generation

Hi!
I don't understand something.
If I take private key
0000000000000000000000000000000000000000000000000000000000000001
private key WIF is
KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
public key compressed is
0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

https://segwitaddress.org/ will give me address
3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN
but BTCPY will give me
3CNHUhP3uyB9EUtRLsmvFUmvGdjGdkTxJw

https://segwitaddress.org/bech32/ will give me address
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
but BTCPY will give me
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 (version 0)
bc1pw508d6qejxtdg4y5r3zarvary0c5xw7k8e76x7 (version 1)
bc1zw508d6qejxtdg4y5r3zarvary0c5xw7k6jw83r (version 2)

Why are these differences?!?

version 0.4, pip

Looking forward to seeing release 0.4 as a pip package. Thanks.

Type of multisig is error!

chainside-btcpy (0.3.3)

height: 232023
txid: 701b8cc6652deadd9cfcbfb0ec8da6f957f8dd36c7184af8e2235bd2958a877f


code:

from pprint import pprint
from btcpy.structs.transaction import Transaction

tx = Transaction.unhexlify("01000000017adceb45e0b9d2107145908de1c3f2ca8a764464466823b7967ff8c348971582000000004a0048304502201282a86d2512b5e8ccbe869e8d030178ab57a176c428eefe6e74ec86255c7f80022100d63c9e0a80859afbb432d508642d504802e8a86cab285e454d6d009f7cd4afcc01ffffffff0145df6e0500000000475121037953dbf08030f67352134992643d033417eaa6fcfb770c038f364ff40d7615882100e8f87dd9d24c3a2f102a5a8276c4a8f58176c961dada423b61063a312b7c270e52ae568a0300")
for out in tx.outs:
    pprint(out.to_json())

output:

Type is "nonstandard".

{'n': 0,
 'scriptPubKey': {'asm': 'OP_1 '
                         '037953dbf08030f67352134992643d033417eaa6fcfb770c038f364ff40d761588 '
                         '00e8f87dd9d24c3a2f102a5a8276c4a8f58176c961dada423b61063a312b7c270e '
                         'OP_2 OP_CHECKMULTISIG',
                  'hex': '5121037953dbf08030f67352134992643d033417eaa6fcfb770c038f364ff40d7615882100e8f87dd9d24c3a2f102a5a8276c4a8f58176c961dada423b61063a312b7c270e52ae',
                  'type': 'nonstandard'},
 'value': '0.91152197'}

Then the output of Bitcoin-cli

Type is "multisig"!

"vout": [
    {
      "value": 0.91152197,
      "n": 0,
      "scriptPubKey": {
        "asm": "1 037953dbf08030f67352134992643d033417eaa6fcfb770c038f364ff40d761588 00e8f87dd9d24c3a2f102a5a8276c4a8f58176c961dada423b61063a312b7c270e 2 OP_CHECKMULTISIG",
        "hex": "5121037953dbf08030f67352134992643d033417eaa6fcfb770c038f364ff40d7615882100e8f87dd9d24c3a2f102a5a8276c4a8f58176c961dada423b61063a312b7c270e52ae",
        "reqSigs": 1,
        "type": "multisig",
        "addresses": [
          "13MH4zmU4UT4Ct6BhoRFGjigC8gN9a9FNn"
        ]
      }
    }
  ]

Pb of syntax with version 0.5

Hi,

I've just installed the latest version (0.5) in a Python 3.3 environment.
Installation returns a few warnings suggesting syntax problems.

   Installing collected packages: chainside-btcpy, ecdsa
   Running setup.py install for chainside-btcpy
     D:\tools\python333\python.exe c:\users\admin\appdata\local\temp\tmptmxbw0.py
       File "D:\tools\python333\Lib\site-packages\btcpy\setup.py", line 26
         return func(*args, **kwargs, strict=strict)
                           ^
     SyntaxError: invalid syntax
 
       File "D:\tools\python333\Lib\site-packages\btcpy\structs\script.py", line 774
         return [int(m), *pubkeys, int(n)]
                         ^
     SyntaxError: can use starred expression only as assignment target
 
       File "D:\tools\python333\Lib\site-packages\tests\integration.py", line 132
         self.instance = self.get_script_cls()(*self.get_args(), *args, *scripts)
                                                   ^
     SyntaxError: invalid syntax
 
     removing c:\users\admin\appdata\local\temp\tmptmxbw0.py
     D:\tools\python333\lib\distutils\dist.py:257: UserWarning: Unknown distribution option: 'python_requires'
       warnings.warn(msg)

Problem is confirmed if I try to run a script calling the setup() function.

File "D:\tools\python333\lib\site-packages\btcpy\setup.py", line 26
   return func(*args, **kwargs, strict=strict)
                              ^
SyntaxError: invalid syntax

Does it mean that Python 3.3 isn't supported anymore ?

Openssl removed ripemd160

  File "/usr/lib/python3.10/hashlib.py", line 123, in __get_builtin_constructor
    raise ValueError('unsupported hash type ' + name)
ValueError: unsupported hash type ripemd160

Efficient script matching

At the moment ScriptBuilder tries to match scripts by running their verify() method, which in turn tries to both match a script template and return the pushed values (e.g. public key hash, script hash and so on). It would be useful to have a faster way to match script types, for instance having scripts declare fast matching conditions (e.g. P2SH must start with HASH160, followed by a 20-byte push operation, followed by EQUAL) to be used for template matching and verify() logics can be used to return the pushed data once the fast matching succeeds.

To be clear, an example of P2SH efficient matching might be:

def match(script_bytes):
    return all([script_bytes[0] == 169,
                script_bytes[1] == 20,
                script_bytes[-1] == 135,
                len(script_bytes) == 23])

Installation and Build errors

First, I wonder whether you realize that one cannot pip install chainside-btcpy. This is the message received "Could not find a version that satisfies the requirement chainside-btcpy (from versions:). No matching distribution found for chainside btcpy.

Secondly, when one does a git clone to download the repository, there are SyntaxErrors with the setup.py file. One error samples is as follows:
byte-compiling build/bdist.linux-x86_64/egg/btcpy/structs/hd.py to hd.pyc
File "build/bdist.linux-x86_64/egg/btcpy/structs/hd.py", line 29
class ExtendedKey(HexSerializable, metaclass=ABCMeta):
SyntaxError: invalid syntax ----- arrow is under the equal sign between metaclass=ABCMeta.

This is just one example of SyntaxErrors.

Due to these errors cannot use this library. In the python environment, 'from btcpy.setup import setup' outputs SyntaxError: invalid syntax for line 23 of btcpy/setup.py .

Please advise as to how to properly install.

Support for taproot and Schnorr?

Thank you for publishing this awesome library, IMO it's well designed and does the job well.
Do maintainers plan to include taproot/Schnorr support?

How to spend multiple UTXOs, from different transactions?

In examples:

from btcpy.structs.sig import *
to_spend = Transaction.unhexlify('...')
unsigned = MutableTransction(version=1,
                              ins=[TxIn(txid=to_spend.txid,
                                        txout=0,
                                        script_sig=ScriptSig.empty(),
                                        sequence=Sequence.max())],
                              outs=[TxOut(value=100000,
                                          n=0,
                                          script_pubkey=P2pkhScript(pubk))],
                              locktime=Locktime(0))
solver = P2pkhSolver(privk)
signed = unsigned.spend([to_spend.outs[0]], [solver])

How to approach if to_spend does not cover enough value to feed the outs[0], what if there is need to use two or three TxOut inputs?

Use RFC6979 for signing

ecdsa supports making deterministic signatures with RFC6979 with sign_digest_deterministic(), which should be a drop in replacement.

TestTransaction fails

On a fresh clone of the repository running
python3 unit.py TestTransaction
fails with the following Error:

Traceback (most recent call last): File "unit.py", line 190, in test_txid self.assertEqual(Transaction.unhexlify(data['raw']).txid, data['txid']) File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/structs/transaction.py", line 333, in unhexlify return cls.deserialize(bytearray(unhexlify(string))) File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/structs/transaction.py", line 338, in deserialize result = parser.get_next_tx(cls is MutableTransaction) File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/lib/parsing.py", line 232, in get_next_tx result = Transaction(version, txins, txouts, locktime) File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/structs/transaction.py", line 360, in __init__ if txid != self.txid and txid is not None: File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/structs/transaction.py", line 378, in txid return self.hash() File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/lib/types.py", line 26, in wrapper object.__setattr__(self, cache, method(self)) File "/home/lorb/.local/lib/python3.5/site-packages/btcpy/structs/transaction.py", line 374, in hash return hexlify(stream.hash()[::-1]).decode() AttributeError: 'Stream' object has no attribute 'hash'

This is with Python 3.5.3

Thorough documentation

This library should have a more thorough documentation beyond its README file. Ideally, such documentation should treat every module and every class, explaining the meaning of their parameters at least for the public interface located in btcpy.structs.

ScriptBuilder definition for transaction creation

In the samples on your readme for transaction creation you have the following:

from btcpy.structs.transaction import Transaction, TxIn, Sequence, TxOut, Locktime

script_sig = Script.unhexlify('48304502210083e6e7507e838a190f0443441c0b62d2df94673887f4482e27e89ff415a90392022050575339c649b85c04bb410a00b62325c1b82c537135fa62fb34fae2c9a30b0b01210384478d41e71dc6c3f9edde0f928a47d1b724c05984ebfb4e7d0422e80abe95ff')
script_pubkey = ScriptBuilder.identify('76a914905f77004d081f20dd421ba5288766d56724c3b288ac')
tx = Transaction(version=1,
... ins=[TxIn(txid='1a5a4f9a0d34cfca187db4fe6a3316f46264984c4b4c9fdb582123815afd508f',
... txout=0,
... script_sig=script_sig,
... sequence=Sequence.max())],
... outs=[TxOut(value=193000000,
... n=0,
... script_pubkey=script_pubkey)],
... locktime=Locktime(0))
tx.txid
'14e6afbae7d2b1825b7ee711cbcad77d519767b70f5a1e70e5ba7f0bfc902e81'

However, there is an error regarding ScriptBuilder when get to:
script_pubkey = ScriptBuilder.identify('76a914905f77004d081f20dd421ba5288766d56724c3b288ac')

The error is ScriptBuidler is not defined.

In fact many of the samples you have on the readme do not work when someone goes through it with regards to transaction creation. Another issue is using from btcpy.structs.script import StackData, empty_script. Error is cannot import name 'empty_script.'

Does someone take the time to go through the steps of what is shown on the readme file?

Unexpected behaviour from P2pkhScript.to_address

The to_address() method on the P2pkhScript returns a p2sh address, while the address() method returns the expected p2pkh address. This happens because the class itself does not implement the to_address() and only inherits it from ScriptPubKey where it is hardcoded to always give a p2sh if it's not segwit. I can see two ways to fix this, either in the parent or the child class:
In the parent class change the address() method to default to p2sh and change the else part in to_address to return self.address(). This preserves the old behaviour for child classes that do not implement address() but fixes it for those that do.
Or change the child class(es) to implement to_address().

Edit: created a PR with a proposed solution (only touches parent class)

Below a minimal test case that shows the unexpected/surprising behaviour:


from btcpy.structs.script import P2pkhScript
from btcpy.structs.crypto import PublicKey

btcpy.setup.setup('mainnet')
script = P2pkhScript(PublicKey.unhexlify('02f1a8ff45acaf2c4405436e44302fcf5e56c1c366e5ad2aebb148e8de4aa92341'))
print(script.to_address()) # 39GzTi9aMuVUwPX9DxxokygtLNvfBLzPhR
print(script.address()) # 1HXWXZovrY7rT7TFQq2dmd9knPFYAXa6VG
assert script.to_address() == script.address()

Incompatibility with Python 3 versions lower than 3.5

It appears that the library is incompatible with Python versions lower than 3.5, despite claiming to be fully Python 3 compatible.

The issue stems from the following idiom mainly:

 m, *pubkeys, n = args

Unpacking rules were much stricter prior to Python 3.5 and did not allow this type of argument unpacking where a single argument follows star arguments.

In order for the library to be compatible with Python 3 versions lower than 3.5, it'd have to use the following idiom instead:

 m, n, *pubkeys = args

This appears to happen in quite a few places. Python 3.4 compatibility would be nice because that version of Python 3 often comes preinstalled with many systems.

Reference: https://www.python.org/dev/peps/pep-0448/

Hello, I am having the following issues to install.

C:\Users\Leon Hatori>pip install chainside-btcpy
Collecting chainside-btcpy
C:\Python27\lib\site-packages\pip-9.0.1-py2.7.egg\pip_vendor\requests\packages\urllib3\util\ssl_.py:318: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/security.html#snimissingwarning.
SNIMissingWarning
C:\Python27\lib\site-packages\pip-9.0.1-py2.7.egg\pip_vendor\requests\packages\urllib3\util\ssl_.py:122: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/security.html#insecureplatformwarning.
InsecurePlatformWarning
Could not find a version that satisfies the requirement chainside-btcpy (from versions: )
No matching distribution found for chainside-btcpy

Creating and signing the P2WSH-over-P2SH transaction

I'm facing a problem with creating a transaction P2WSH-over-P2SH, it's not a bug or anything, I just can't figure it out seeking for some help.

So here is the problem, I have a mutlisig wallet with 2 private keys on testnet.

p_key1 = 'b4741e2e87c19256c81a0662480b75f15831c7017d28b4586882c52b89f91bcf'
p_key2 = '3c212d5724a2a6bd5004c502a4be7ba55a1cf4a0923ffd1a9bb3cb6fd7201bed'

Then I use this function to generate a P2shScript:

def p2sh_script(pub_key_1, pub_key_2):
    witness_script = MultisigScript(2, pub_key_1, pub_key_2, 2)
    witness_script_hash = witness_script.p2wsh_hash()
    redeem_script = P2wshV0Script(witness_script_hash)
    script_pub_key = P2shScript(redeem_script)
    return (witness_script, redeem_script, script_pub_key)

It works fine, gives me the right address with which I take out unspents via API call.

And here is the 3 functions which I'm kinda confused about and can't figure what exactly I did wrong, I know for a fact that after signing a transaction I don't have anything in scriptSig in one of the inputs, I tried to play it around and read a documentation on P2WSH-over-P2SH in different sources, but that didn't helped me implement it with your library. Maybe you could help me out on that one @SimoneBronzini

def build_transaction(utxos, to, amount):
    witness_script = address['witness_script']
    redeem_script = address['redeem_script']
    script_pubkey = address['script_pubkey']

    # goes into input as a witness
    print(f'Witness Script for address `{script_pubkey.address()}`:\n{witness_script.hexlify()}')

    # goes into input as a script
    print(f'Redeem Script for address `{script_pubkey.address()}`:\n{redeem_script.hexlify()}')

    # goes to output which will be the final value of address
    print(f'Script Pub Key for address `{script_pubkey.address()}`:\n{script_pubkey.hexlify()}')
    

    ins, total = add_inputs(utxos, amount)

    # Generating output script for transaction
    to_send_address = P2shAddress.from_string(to)
    to_send_address_hash = to_send_address.hash
    to_send_script_pubkey = P2shScript(to_send_address_hash)
    
    # goes to output for amount which will get
    print(f'Script Pub Key for address `{to_send_address}`:\n{to_send_script_pubkey.hexlify()}')
    address['to_script'] = to_send_script_pubkey

    outs = []
    outs.append(
        TxOut(
            value=amount,
            n=0,
            script_pubkey=to_send_script_pubkey
        )
    )

    fee = address['fee']

    if total > amount + fee:
        outs.append(
            TxOut(
                value=total-amount-fee,
                n=1,
                script_pubkey=script_pubkey
            )
        )

    tx = MutableTransaction(
        version=0,
        ins=ins,
        outs=outs,
        locktime=Locktime(0)
    )

    return tx

def send():
    amount = currency_to_satoshi_cached(100, 'usd')
    fee = get_fee_cached()
    address['fee'] = fee
    # Get unspents
    utxos = get_utxos()
    #print(f'UTXOS: {utxos}')
    # Create a new unsigned transaction
    unsigned_tx = build_transaction(utxos, patau_wallet, amount)
    print(f'Unsigned Transaction: { unsigned_tx.hexlify() }')

    # Create a MultisigSolver for our address
    multisig_solver = MultisigSolver(address['pkey1'], address['pkey2'])

    # Create a P2shSolver for our P2shScript, which will solve our MultisigScript
    witness_solver = P2wshV0Solver(address['witness_script'], multisig_solver)
    script_solver = P2shSolver(address['redeem_script'], witness_solver)

    # TODO Sign an unsigned transaction
    signed_tx = unsigned_tx.spend(unsigned_tx.outs, [witness_solver, script_solver])
    print(f'Signed Transaction: { signed_tx.hexlify() }')
    
    # TODO Broadcast signed transaction to network
    # NetworkAPI.broadcast_tx_testnet(signed_tx.hexlify())

def add_inputs(unspents, amount):
    ins = []
    total = 0
    for unspent in unspents:
        script = ScriptSig.empty()
        txid = unspent.txid
        tx_index = unspent.txindex
        uns_amount = unspent.amount

        total += uns_amount
        ins.append(TxIn(
            txid=txid,
            txout=tx_index,
            script_sig=script,
            sequence=Sequence.max()
        ))
    
    return (ins, total)

Verification of Bitcoin taproot address

Hey,

Using you library for BTC address verification. Would it be possible to verify new Bitcoin Taproot address e.g. bc1pmzfrwwndsqmk5yh69yjr5lfgfg4ev8c0tsc06e

It is possible to verify legacy btc addresses and segwit addresses however not taproot addresses.
Is it somehow possible to do workaround or are you planning to add this?

Thank you very much in advance and appreciate you work!!!

Maybe the default version of segwit addresses should be zero

If I do:

>>> pubKey = PublicKey.unhexlify('034486893a383d2b5d6c1c2ea72b918f5ce3b14b1737ecd987faffa1a4f954a797')
>>> str(pubKey.to_segwit_address())

This gives me:

'bc1pna5zac9rgecdktqfs920me8x6ys7ad5fsdw3dh'

Which is not the address I'm looking for. If I however explicitly specify the segwit program version:

>>> str(pubKey.to_segwit_address(0))

I finally get:

'bc1qna5zac9rgecdktqfs920me8x6ys7ad5fmne6qu'

It would be easy to submit a PR with this change, but I'm new to this library and don't know if this would break things or go somewhat against the desired behavior. I'm just saying that, as a user, at this point I expect the version to be zero by default.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.