Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion bitcointx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright (C) 2012-2018 The python-bitcoinlib developers
# Copyright (C) 2018-2019 The python-bitcointx developers
# Copyright (C) 2018-2021 The python-bitcointx developers
#
# This file is part of python-bitcointx.
#
Expand Down Expand Up @@ -310,6 +310,33 @@ def select_chain_params(params: Union[str, ChainParamsBase,
return prev_params, params


def allow_secp256k1_experimental_modules() -> None:
"""
python-bitcointx uses libsecp256k1 via ABI using ctypes, using dynamic
library loading. This means that the correctness of the calls to secp256k1
functions depend on these function definitions in python-bitcointx code
to be in-sync with the actual C language definitions in the library when
it was compiled. Libsecp256k1 ABI is mostly stable, but still has no
officially released version at the moment. But for schnorr signatures
and x-only pubkeys, we have to use the modules of libsecp256k1 that are
currently marked as 'experimental'. These modules being experimental
mean that their ABI can change at any moment. Therefore, to use
python-bitcointx with this functionality, it is highly recommended to
compile your own version libsecp256k1 using the git commit that is
recommended for particular version of python-bitcointx, and then make
sure that python-bitcointx will load that exact version of libsecp256k1
with set_custom_secp256k1_path() or LD_LIBRARY_PATH environment variable.

But since using experimental modules with some over version of libsecp256k1
may lead to crashes, using them is disabled by default. One have to
explicitly enable this by calling this function, or setting the
environment variable
"PYTHON_BITCOINTX_ALLOW_LIBSECP256K1_EXPERIMENTAL_MODULES_USE" to the
value "1".
"""
bitcointx.util._allow_secp256k1_experimental_modules = True


def set_custom_secp256k1_path(path: str) -> None:
"""Set the custom path that will be used to load secp256k1 library
by bitcointx.core.secp256k1 module. For the calling of this
Expand Down Expand Up @@ -355,6 +382,14 @@ class ChainParamsContextVar(bitcointx.util.ContextVarsCompat):

_chain_params_context = ChainParamsContextVar(params=BitcoinMainnetParams())

_secp256k1_experimental_modues_enable_var = os.environ.get(
'PYTHON_BITCOINTX_ALLOW_LIBSECP256K1_EXPERIMENTAL_MODULES_USE')

if _secp256k1_experimental_modues_enable_var is not None:
assert _secp256k1_experimental_modues_enable_var in ("0", "1")
if int(_secp256k1_experimental_modues_enable_var):
allow_secp256k1_experimental_modules()


__all__ = (
'ChainParamsBase',
Expand All @@ -369,4 +404,5 @@ class ChainParamsContextVar(bitcointx.util.ContextVarsCompat):
'find_chain_params',
'set_custom_secp256k1_path',
'get_custom_secp256k1_path',
'allow_secp256k1_experimental_modules',
)
15 changes: 14 additions & 1 deletion bitcointx/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@

from ..util import (
no_bool_use_as_property, ClassMappingDispatcher, activate_class_dispatcher,
dispatcher_wrap_methods, classgetter, ensure_isinstance, ContextVarsCompat
dispatcher_wrap_methods, classgetter, ensure_isinstance, ContextVarsCompat,
tagged_hasher
)

# NOTE: due to custom class dispatching and mutable/immmutable
Expand Down Expand Up @@ -216,11 +217,23 @@ class CoreCoinParams(CoreCoinClass, next_dispatch_final=True):
def MAX_MONEY(cls) -> int:
return 21000000 * cls.COIN

TAPROOT_LEAF_TAPSCRIPT: int
taptweak_hasher: Callable[[bytes], bytes]
tapleaf_hasher: Callable[[bytes], bytes]
tapbranch_hasher: Callable[[bytes], bytes]
tap_sighash_hasher: Callable[[bytes], bytes]


class CoreBitcoinParams(CoreCoinParams, CoreBitcoinClass):
PSBT_MAGIC_HEADER_BYTES = b'psbt\xff'
PSBT_MAGIC_HEADER_BASE64 = 'cHNidP'

TAPROOT_LEAF_TAPSCRIPT = 0xc0
taptweak_hasher = tagged_hasher(b'TapTweak')
tapleaf_hasher = tagged_hasher(b'TapLeaf')
tapbranch_hasher = tagged_hasher(b'TapBranch')
tap_sighash_hasher = tagged_hasher(b'TapSighash')


def MoneyRange(nValue: int) -> bool:
# check is in satoshis, supplying float might indicate that
Expand Down
55 changes: 45 additions & 10 deletions bitcointx/core/bitcoinconsensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
"""

import ctypes
from typing import Union, Tuple, Set, Optional
from typing import Union, Tuple, Set, Optional, Sequence

from bitcointx.util import ensure_isinstance
from bitcointx.core import MoneyRange, CTransaction
from bitcointx.core import MoneyRange, CTransaction, CTxOut
from bitcointx.core.script import CScript, CScriptWitness
from bitcointx.core.scripteval import (
SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_DERSIG,
SCRIPT_VERIFY_NULLDUMMY, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, SCRIPT_VERIFY_WITNESS,
SCRIPT_VERIFY_TAPROOT,
ALL_SCRIPT_VERIFY_FLAGS, ScriptVerifyFlag_Type,
VerifyScriptError, script_verify_flags_to_string
)
Expand Down Expand Up @@ -68,14 +69,16 @@
bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY = 1 << 10
# enable WITNESS (BIP141)
bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS = 1 << 11
bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT = 1 << 17

BITCOINCONSENSUS_FLAG_MAPPING = {
SCRIPT_VERIFY_P2SH: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_P2SH,
SCRIPT_VERIFY_DERSIG: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_DERSIG,
SCRIPT_VERIFY_NULLDUMMY: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_NULLDUMMY,
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY,
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY,
SCRIPT_VERIFY_WITNESS: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS
SCRIPT_VERIFY_WITNESS: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS,
SCRIPT_VERIFY_TAPROOT: bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT
}

BITCOINCONSENSUS_ACCEPTED_FLAGS = set(BITCOINCONSENSUS_FLAG_MAPPING.keys())
Expand Down Expand Up @@ -123,6 +126,20 @@ def _add_function_definitions(handle: ctypes.CDLL) -> None:
ctypes.POINTER(ctypes.c_uint) # bitcoinconsensus_error* err
]

# handle.bitcoinconsensus_verify_script_taproot.restype = ctypes.c_int
# handle.bitcoinconsensus_verify_script_taproot.argtypes = [
# ctypes.c_char_p, # const unsigned char *scriptPubKey
# ctypes.c_uint, # unsigned int scriptPubKeyLen
# ctypes.c_int64, # int64_t amount
# ctypes.c_char_p, # const unsigned char *txTo
# ctypes.c_uint, # unsigned int txToLen
# ctypes.c_char_p, # const unsigned char *spentOutputs
# ctypes.c_uint, # unsigned int spentOutputsLen
# ctypes.c_uint, # unsigned int nIn
# ctypes.c_uint, # unsigned int flags
# ctypes.POINTER(ctypes.c_uint) # bitcoinconsensus_error* err
# ]

handle.bitcoinconsensus_version.restype = ctypes.c_int
handle.bitcoinconsensus_version.argtypes = []

Expand Down Expand Up @@ -173,12 +190,15 @@ def load_bitcoinconsensus_library(library_name: Optional[str] = None,


def ConsensusVerifyScript(
scriptSig: CScript, scriptPubKey: CScript,
txTo: CTransaction, inIdx: int,
scriptSig: CScript,
scriptPubKey: CScript,
txTo: CTransaction,
inIdx: int,
flags: Union[Tuple[ScriptVerifyFlag_Type, ...],
Set[ScriptVerifyFlag_Type]] = (),
amount: int = 0,
witness: Optional[CScriptWitness] = None,
spent_outputs: Optional[Sequence[CTxOut]] = None,
consensus_library_hanlde: Optional[ctypes.CDLL] = None
) -> None:

Expand Down Expand Up @@ -266,11 +286,26 @@ def ConsensusVerifyScript(
error_code = ctypes.c_uint()
error_code.value = 0

result = handle.bitcoinconsensus_verify_script_with_amount(
scriptPubKey, len(scriptPubKey), amount,
tx_data, len(tx_data), inIdx, libconsensus_flags,
ctypes.byref(error_code)
)
if spent_outputs:
raise NotImplementedError(
'no taproot support for libbitcoinconsensus yet')
if len(spent_outputs) != len(txTo.vin):
raise ValueError('number of spent_outputs must equal '
'the number of inputs in transacton')

spent_outs_data = b''.join(out.serialize() for out in spent_outputs)
result = handle.bitcoinconsensus_verify_script_taproot(
scriptPubKey, len(scriptPubKey), amount,
tx_data, len(tx_data), spent_outs_data, len(spent_outs_data),
inIdx, libconsensus_flags,
ctypes.byref(error_code)
)
else:
result = handle.bitcoinconsensus_verify_script_with_amount(
scriptPubKey, len(scriptPubKey), amount,
tx_data, len(tx_data), inIdx, libconsensus_flags,
ctypes.byref(error_code)
)

if result == 1:
# script was verified successfully - just return, no exception raised.
Expand Down
Loading