diff --git a/jmbitcoin/jmbitcoin/secp256k1_transaction.py b/jmbitcoin/jmbitcoin/secp256k1_transaction.py index 8dfc3c5..d58deb7 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_transaction.py +++ b/jmbitcoin/jmbitcoin/secp256k1_transaction.py @@ -88,6 +88,7 @@ def there_is_one_segwit_input(input_types: List[str]) -> bool: # since each may have a different size of witness; in # that case, the internal list in this list comprehension # will need updating. + # note that there is no support yet for paying *from* p2tr. return any(y in ["p2sh-p2wpkh", "p2wpkh", "p2wsh"] for y in input_types) def estimate_tx_size(ins: List[str], outs: List[str]) -> Union[int, Tuple[int]]: @@ -123,21 +124,30 @@ def estimate_tx_size(ins: List[str], outs: List[str]) -> Union[int, Tuple[int]]: # script's redeemscript field in the witness, but for arbitrary scripts, # the witness portion could be any other size. # Hence, we may need to modify this later. + # + # Note that there is no support yet for spending *from* p2tr: + # we should fix this soon, since it is desirable to be able to support + # coinjoins with counterparties sending taproot, but note, JM coinjoins + # do not allow non-standard (usually v0 segwit) inputs, anyway. inmults = {"p2wsh": {"w": 1 + 72 + 43, "nw": 41}, "p2wpkh": {"w": 108, "nw": 41}, "p2sh-p2wpkh": {"w": 108, "nw": 64}, "p2pkh": {"w": 0, "nw": 148}} # Notes: in outputs, there is only 1 'scripthash' - # type for either segwit/nonsegwit. + # type for either segwit/nonsegwit (hence "p2sh-p2wpkh" + # is a bit misleading, but is kept to the same as inputs, + # for simplicity. See notes on inputs above). # p2wsh has structure 8 bytes output, then: # x22,x00,x20,(32 byte hash), so 32 + 3 + 8 # note also there is no need to distinguish witness # here, outputs are always entirely nonwitness. + # p2tr is also 32 byte hash with x01 instead of x00 version. outmults = {"p2wsh": 43, "p2wpkh": 31, "p2sh-p2wpkh": 32, - "p2pkh": 34} + "p2pkh": 34, + "p2tr": 43} # nVersion, nLockTime, nins, nouts: nwsize = 4 + 4 + 2 diff --git a/jmbitcoin/test/test_tx_signing.py b/jmbitcoin/test/test_tx_signing.py index bfdb946..ebe73ef 100644 --- a/jmbitcoin/test/test_tx_signing.py +++ b/jmbitcoin/test/test_tx_signing.py @@ -36,13 +36,14 @@ from math import ceil (["p2pkh"], ["p2pkh", "p2sh-p2wpkh"], 224), (["p2sh-p2wpkh"], ["p2sh-p2wpkh"], 134), (["p2wpkh"], ["p2wpkh"], 110), + (["p2wpkh"], ["p2wpkh", "p2tr"], 153), ]) def test_tx_size_estimate(inaddrtypes, outaddrtypes, size_expected): # non-sw only inputs result in a single integer return, # segwit inputs return (witness size, non-witness size) x = btc.estimate_tx_size(inaddrtypes, outaddrtypes) if btc.there_is_one_segwit_input(inaddrtypes): - s = ceil((x[0] + x[1] * 4)/4.0) + s = ceil((x[0] + x[1] * 4) / 4.0) else: s = x assert s == size_expected diff --git a/jmclient/jmclient/cryptoengine.py b/jmclient/jmclient/cryptoengine.py index 70f35f7..065e3ee 100644 --- a/jmclient/jmclient/cryptoengine.py +++ b/jmclient/jmclient/cryptoengine.py @@ -16,7 +16,7 @@ from .configure import get_network, jm_single # make existing wallets unsable. TYPE_P2PKH, TYPE_P2SH_P2WPKH, TYPE_P2WPKH, TYPE_P2SH_M_N, TYPE_TIMELOCK_P2WSH, \ TYPE_SEGWIT_WALLET_FIDELITY_BONDS, TYPE_WATCHONLY_FIDELITY_BONDS, \ - TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2WPKH, TYPE_P2WSH = range(10) + TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2WPKH, TYPE_P2WSH, TYPE_P2TR = range(11) NET_MAINNET, NET_TESTNET, NET_SIGNET = range(3) NET_MAP = {'mainnet': NET_MAINNET, 'testnet': NET_TESTNET, 'signet': NET_SIGNET} @@ -52,6 +52,8 @@ def detect_script_type(script_str): return TYPE_P2WPKH elif script.is_witness_v0_scripthash(): return TYPE_P2WSH + elif script.is_witness_v1_taproot(): + return TYPE_P2TR raise EngineError("Unknown script type for script '{}'" .format(bintohex(script_str))) @@ -224,6 +226,12 @@ class BTCEngine(object): stype = detect_script_type(script) assert stype in ENGINES engine = ENGINES[stype] + # TODO though taproot is currently a returnable + # type from detect_script_type, there is not yet + # a corresponding ENGINE, thus a None return is possible. + # Callers recognize this as EngineError. + if engine is None: + raise EngineError pscript = engine.pubkey_to_script(pubkey) return script == pscript @@ -457,5 +465,6 @@ ENGINES = { TYPE_TIMELOCK_P2WSH: BTC_Timelocked_P2WSH, TYPE_WATCHONLY_TIMELOCK_P2WSH: BTC_Watchonly_Timelocked_P2WSH, TYPE_WATCHONLY_P2WPKH: BTC_Watchonly_P2WPKH, - TYPE_SEGWIT_WALLET_FIDELITY_BONDS: BTC_P2WPKH + TYPE_SEGWIT_WALLET_FIDELITY_BONDS: BTC_P2WPKH, + TYPE_P2TR: None # TODO } diff --git a/jmclient/jmclient/taker_utils.py b/jmclient/jmclient/taker_utils.py index a500fd2..7ed235e 100644 --- a/jmclient/jmclient/taker_utils.py +++ b/jmclient/jmclient/taker_utils.py @@ -121,12 +121,14 @@ def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False, # we don't recognize the destination script type, # so set it as the same as the change (which will usually # be the same as the spending wallet, but see above for custom) + # Notice that this is handled differently to the sweep case above, + # because we must use a list - there is more than one output outtype = change_type outtypes = [change_type, outtype] # not doing a sweep; we will have change. # 8 inputs to be conservative; note we cannot account for the possibility # of non-standard input types at this point. - initial_fee_est = estimate_tx_fee(8,2, txtype=txtype, outtype=outtypes) + initial_fee_est = estimate_tx_fee(8, 2, txtype=txtype, outtype=outtypes) utxos = wallet_service.select_utxos(mixdepth, amount + initial_fee_est, includeaddr=True) script_types = get_utxo_scripts(wallet_service.wallet, utxos) diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index f2892d9..094a326 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -27,8 +27,8 @@ from .support import select_gradual, select_greedy, select_greediest, \ select, NotEnoughFundsException from .cryptoengine import TYPE_P2PKH, TYPE_P2SH_P2WPKH, TYPE_P2WSH,\ TYPE_P2WPKH, TYPE_TIMELOCK_P2WSH, TYPE_SEGWIT_WALLET_FIDELITY_BONDS,\ - TYPE_WATCHONLY_FIDELITY_BONDS, TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2WPKH,\ - ENGINES, detect_script_type, EngineError + TYPE_WATCHONLY_FIDELITY_BONDS, TYPE_WATCHONLY_TIMELOCK_P2WSH, \ + TYPE_WATCHONLY_P2WPKH, TYPE_P2TR, ENGINES, detect_script_type, EngineError from .support import get_random_bytes from . import mn_encode, mn_decode import jmbitcoin as btc @@ -92,7 +92,7 @@ def estimate_tx_fee(ins, outs, txtype='p2pkh', outtype=None, extra_bytes=0): # See docstring for explanation: if isinstance(txtype, str): - ins = [txtype]* ins + ins = [txtype] * ins else: assert isinstance(txtype, list) ins = txtype @@ -505,6 +505,8 @@ class BaseWallet(object): return 'p2sh-p2wpkh' elif script_type == TYPE_P2WSH: return 'p2wsh' + elif script_type == TYPE_P2TR: + return 'p2tr' # should be unreachable; all possible returns # from detect_script_type are covered. assert False