Browse Source

support p2tr outputs in size estimation

master
Adam Gibson 3 years ago
parent
commit
357b611655
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 14
      jmbitcoin/jmbitcoin/secp256k1_transaction.py
  2. 3
      jmbitcoin/test/test_tx_signing.py
  3. 13
      jmclient/jmclient/cryptoengine.py
  4. 4
      jmclient/jmclient/taker_utils.py
  5. 8
      jmclient/jmclient/wallet.py

14
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

3
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

13
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
}

4
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)

8
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

Loading…
Cancel
Save