Browse Source

Update signature exchange and verification for bech32

master
Jules Comte 5 years ago
parent
commit
2ae348b988
  1. 11
      jmbitcoin/jmbitcoin/secp256k1_transaction.py
  2. 36
      jmclient/jmclient/maker.py
  3. 44
      jmclient/jmclient/taker.py
  4. 4
      jmclient/test/test_coinjoin.py
  5. 2
      jmclient/test/test_tx_creation.py

11
jmbitcoin/jmbitcoin/secp256k1_transaction.py

@ -236,6 +236,7 @@ def sign(tx, i, priv, hashcode=SIGHASH_ALL, amount=None, native=False):
# https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252
flags.add(SCRIPT_VERIFY_P2SH)
flags.add(SCRIPT_VERIFY_WITNESS)
if native and native != "p2wpkh":
scriptCode = native
@ -255,10 +256,9 @@ def sign(tx, i, priv, hashcode=SIGHASH_ALL, amount=None, native=False):
sig = ecdsa_raw_sign(sighash, priv, rawmsg=True) + bytes([hashcode])
except Exception as e:
return return_err(e)
if native:
flags.add(SCRIPT_VERIFY_WITNESS)
else:
if not native:
tx.vin[i].scriptSig = CScript([input_scriptPubKey])
input_scriptPubKey = pubkey_to_p2sh_p2wpkh_script(pub)
if native and native != "p2wpkh":
witness = [sig, scriptCode]
@ -320,12 +320,11 @@ def make_shuffled_tx(ins, outs, version=1, locktime=0):
random.shuffle(outs)
return mktx(ins, outs, version=version, locktime=locktime)
def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None,
witness=None, native=False):
def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None, witness=None):
flags = set([SCRIPT_VERIFY_STRICTENC])
if witness:
# https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252
flags.add(SCRIPT_VERIFY_P2SH)
if native:
flags.add(SCRIPT_VERIFY_WITNESS)
try:
VerifyScript(scriptSig, scriptPubKey, tx, i,

36
jmclient/jmclient/maker.py

@ -156,29 +156,19 @@ class Maker(object):
success, msg = self.wallet_service.sign_tx(tx, our_inputs)
assert success, msg
for index in our_inputs:
sigmsg = tx.vin[index].scriptSig
if tx.has_witness():
# Note that this flag only implies that the transaction
# *as a whole* is using segwit serialization; it doesn't
# imply that this specific input is segwit type (to be
# fully general, we allow that even our own wallet's
# inputs might be of mixed type). So, we catch the EngineError
# which is thrown by non-segwit types. This way the sigmsg
# will only contain the scriptCode field if the wallet object
# decides it's necessary/appropriate for this specific input
# If it is segwit, we prepend the witness data since we want
# (sig, pub, witnessprogram=scriptSig - note we could, better,
# pass scriptCode here, but that is not backwards compatible,
# as the taker uses this third field and inserts it into the
# transaction scriptSig), else (non-sw) the !sig message remains
# unchanged as (sig, pub).
try:
sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)]
scriptCode = btc.pubkey_to_p2wpkh_script(pub)
sigmsg = btc.CScript([sig]) + btc.CScript(pub) + scriptCode
except Exception as e:
#the sigmsg was already set before the segwit check
pass
# The second case here is kept for backwards compatibility.
if self.wallet_service.get_txtype() == 'p2pkh':
sigmsg = tx.vin[index].scriptSig
elif self.wallet_service.get_txtype() == 'p2sh-p2wpkh':
sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)]
scriptCode = btc.pubkey_to_p2wpkh_script(pub)
sigmsg = btc.CScript([sig]) + btc.CScript(pub) + scriptCode
elif self.wallet_service.get_txtype() == 'p2wpkh':
sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)]
sigmsg = btc.CScript([sig]) + btc.CScript(pub)
else:
jlog.error("Taker has unknown wallet type")
sys.exit(EXIT_FAILURE)
sigs.append(base64.b64encode(sigmsg).decode('ascii'))
return (True, sigs)

44
jmclient/jmclient/taker.py

@ -577,42 +577,38 @@ class Taker(object):
jlog.debug("Junk signature: " + str(sig_deserialized) + \
", not attempting to verify")
break
# The second case here is kept for backwards compatibility.
if len(sig_deserialized) == 2:
ver_sig, ver_pub = sig_deserialized
scriptCode = None
elif len(sig_deserialized) == 3:
ver_sig, ver_pub, scriptCode = sig_deserialized
ver_sig, ver_pub, _ = sig_deserialized
else:
jlog.debug("Invalid signature message - not 2 or 3 items")
break
ver_amt = utxo_data[i]['value'] if scriptCode else None
scriptPubKey = btc.CScript(utxo_data[i]['script'])
is_witness_input = scriptPubKey.is_p2sh() or scriptPubKey.is_witness_v0_keyhash()
ver_amt = utxo_data[i]['value'] if is_witness_input else None
witness = btc.CScriptWitness(
[ver_sig, ver_pub]) if scriptCode else None
[ver_sig, ver_pub]) if is_witness_input else None
# don't attempt to parse `pub` as pubkey unless it's valid.
if scriptCode:
if scriptPubKey.is_p2sh():
try:
s = btc.pubkey_to_p2wpkh_script(ver_pub)
except:
jlog.debug("Junk signature message, invalid pubkey, ignoring.")
break
scriptSig = btc.CScript([ver_sig, ver_pub]) if not scriptCode else btc.CScript([s])
# Pre-Feb 2020, we used the third field scriptCode differently in
# pre- and post-0.5.0; now the scriptCode is implicit (i.e. calculated
# by underlying library, so that exceptional case is covered.
sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig,
btc.CScript(utxo_data[i]['script']), amount=ver_amt, witness=witness)
if scriptPubKey.is_witness_v0_keyhash():
scriptSig = btc.CScript(b'')
elif scriptPubKey.is_p2sh():
scriptSig = btc.CScript([s])
else:
scriptSig = btc.CScript([ver_sig, ver_pub])
# verification for the native case is functionally identical but
# adds another flag; so we can allow it here:
if not sig_good:
sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig,
btc.CScript(utxo_data[i]['script']), amount=ver_amt,
witness=witness, native=True)
# if passes, below code executes, and we should change for native:
scriptSig = btc.CScript([b""])
sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig,
scriptPubKey, amount=ver_amt, witness=witness)
if sig_good:
jlog.debug('found good sig at index=%d' % (u[0]))
@ -622,7 +618,7 @@ class Taker(object):
# there is an assumption of p2sh-p2wpkh or p2wpkh, for the segwit
# case.
self.latest_tx.vin[u[0]].scriptSig = scriptSig
if ver_amt:
if is_witness_input:
self.latest_tx.wit.vtxinwit[u[0]] = btc.CTxInWitness(
btc.CScriptWitness(witness))
inserted_sig = True
@ -647,8 +643,9 @@ class Taker(object):
# other guy sent a failed signature
tx_signed = True
for ins in self.latest_tx.vin:
if ins.scriptSig == b"":
for input, witness in zip(self.latest_tx.vin, self.latest_tx.wit.vtxinwit):
if input.scriptSig == b"" \
and witness == btc.CTxInWitness(btc.CScriptWitness([])):
tx_signed = False
if not tx_signed:
return False
@ -774,7 +771,8 @@ class Taker(object):
utxo = (ins.prevout.hash[::-1], ins.prevout.n)
if utxo not in self.input_utxos.keys():
continue
script = self.input_utxos[utxo]["script"]
self.latest_tx.vin[index].scriptSig = btc.CScript(b'')
script = self.input_utxos[utxo]['script']
amount = self.input_utxos[utxo]['value']
our_inputs[index] = (script, amount)
success, msg = self.wallet_service.sign_tx(self.latest_tx, our_inputs)

4
jmclient/test/test_coinjoin.py

@ -11,7 +11,7 @@ from twisted.internet import reactor
from jmbase import get_log, hextobin
from jmclient import load_test_config, jm_single,\
YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet,\
YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet, SegwitWallet,\
NO_ROUNDING
from jmclient.podle import set_commitment_file
from commontest import make_wallets, default_max_cj_fee
@ -118,7 +118,7 @@ def do_tx_signing(taker, makers, active_orders, txdata):
return taker_final_result
@pytest.mark.parametrize('wallet_cls', (LegacyWallet, SegwitLegacyWallet))
@pytest.mark.parametrize('wallet_cls', (LegacyWallet, SegwitLegacyWallet, SegwitWallet))
def test_simple_coinjoin(monkeypatch, tmpdir, setup_cj, wallet_cls):
def raise_exit(i):
raise Exception("sys.exit called")

2
jmclient/test/test_tx_creation.py

@ -70,7 +70,7 @@ def test_verify_tx_input(setup_tx_creation):
res = bitcoin.verify_tx_input(tx2, 0, scriptSig,
bitcoin.pubkey_to_p2sh_p2wpkh_script(pub),
amount = bitcoin.coins_to_satoshi(1),
witness = bitcoin.CScript([sig, pub]))
witness = bitcoin.CScriptWitness([sig, pub]))
assert res
def test_absurd_fees(setup_tx_creation):

Loading…
Cancel
Save