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 # https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252
flags.add(SCRIPT_VERIFY_P2SH) flags.add(SCRIPT_VERIFY_P2SH)
flags.add(SCRIPT_VERIFY_WITNESS)
if native and native != "p2wpkh": if native and native != "p2wpkh":
scriptCode = native 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]) sig = ecdsa_raw_sign(sighash, priv, rawmsg=True) + bytes([hashcode])
except Exception as e: except Exception as e:
return return_err(e) return return_err(e)
if native: if not native:
flags.add(SCRIPT_VERIFY_WITNESS)
else:
tx.vin[i].scriptSig = CScript([input_scriptPubKey]) tx.vin[i].scriptSig = CScript([input_scriptPubKey])
input_scriptPubKey = pubkey_to_p2sh_p2wpkh_script(pub)
if native and native != "p2wpkh": if native and native != "p2wpkh":
witness = [sig, scriptCode] witness = [sig, scriptCode]
@ -320,12 +320,11 @@ def make_shuffled_tx(ins, outs, version=1, locktime=0):
random.shuffle(outs) random.shuffle(outs)
return mktx(ins, outs, version=version, locktime=locktime) return mktx(ins, outs, version=version, locktime=locktime)
def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None, def verify_tx_input(tx, i, scriptSig, scriptPubKey, amount=None, witness=None):
witness=None, native=False):
flags = set([SCRIPT_VERIFY_STRICTENC]) flags = set([SCRIPT_VERIFY_STRICTENC])
if witness: if witness:
# https://github.com/Simplexum/python-bitcointx/blob/648ad8f45ff853bf9923c6498bfa0648b3d7bcbd/bitcointx/core/scripteval.py#L1250-L1252
flags.add(SCRIPT_VERIFY_P2SH) flags.add(SCRIPT_VERIFY_P2SH)
if native:
flags.add(SCRIPT_VERIFY_WITNESS) flags.add(SCRIPT_VERIFY_WITNESS)
try: try:
VerifyScript(scriptSig, scriptPubKey, tx, i, 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) success, msg = self.wallet_service.sign_tx(tx, our_inputs)
assert success, msg assert success, msg
for index in our_inputs: for index in our_inputs:
sigmsg = tx.vin[index].scriptSig # The second case here is kept for backwards compatibility.
if tx.has_witness(): if self.wallet_service.get_txtype() == 'p2pkh':
# Note that this flag only implies that the transaction sigmsg = tx.vin[index].scriptSig
# *as a whole* is using segwit serialization; it doesn't elif self.wallet_service.get_txtype() == 'p2sh-p2wpkh':
# imply that this specific input is segwit type (to be sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)]
# fully general, we allow that even our own wallet's scriptCode = btc.pubkey_to_p2wpkh_script(pub)
# inputs might be of mixed type). So, we catch the EngineError sigmsg = btc.CScript([sig]) + btc.CScript(pub) + scriptCode
# which is thrown by non-segwit types. This way the sigmsg elif self.wallet_service.get_txtype() == 'p2wpkh':
# will only contain the scriptCode field if the wallet object sig, pub = [a for a in iter(tx.wit.vtxinwit[index].scriptWitness)]
# decides it's necessary/appropriate for this specific input sigmsg = btc.CScript([sig]) + btc.CScript(pub)
# If it is segwit, we prepend the witness data since we want else:
# (sig, pub, witnessprogram=scriptSig - note we could, better, jlog.error("Taker has unknown wallet type")
# pass scriptCode here, but that is not backwards compatible, sys.exit(EXIT_FAILURE)
# 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
sigs.append(base64.b64encode(sigmsg).decode('ascii')) sigs.append(base64.b64encode(sigmsg).decode('ascii'))
return (True, sigs) return (True, sigs)

44
jmclient/jmclient/taker.py

@ -577,42 +577,38 @@ class Taker(object):
jlog.debug("Junk signature: " + str(sig_deserialized) + \ jlog.debug("Junk signature: " + str(sig_deserialized) + \
", not attempting to verify") ", not attempting to verify")
break break
# The second case here is kept for backwards compatibility.
if len(sig_deserialized) == 2: if len(sig_deserialized) == 2:
ver_sig, ver_pub = sig_deserialized ver_sig, ver_pub = sig_deserialized
scriptCode = None
elif len(sig_deserialized) == 3: elif len(sig_deserialized) == 3:
ver_sig, ver_pub, scriptCode = sig_deserialized ver_sig, ver_pub, _ = sig_deserialized
else: else:
jlog.debug("Invalid signature message - not 2 or 3 items") jlog.debug("Invalid signature message - not 2 or 3 items")
break 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( 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. # don't attempt to parse `pub` as pubkey unless it's valid.
if scriptCode: if scriptPubKey.is_p2sh():
try: try:
s = btc.pubkey_to_p2wpkh_script(ver_pub) s = btc.pubkey_to_p2wpkh_script(ver_pub)
except: except:
jlog.debug("Junk signature message, invalid pubkey, ignoring.") jlog.debug("Junk signature message, invalid pubkey, ignoring.")
break 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 if scriptPubKey.is_witness_v0_keyhash():
# pre- and post-0.5.0; now the scriptCode is implicit (i.e. calculated scriptSig = btc.CScript(b'')
# by underlying library, so that exceptional case is covered. elif scriptPubKey.is_p2sh():
sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig, scriptSig = btc.CScript([s])
btc.CScript(utxo_data[i]['script']), amount=ver_amt, witness=witness) else:
scriptSig = btc.CScript([ver_sig, ver_pub])
# verification for the native case is functionally identical but sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig,
# adds another flag; so we can allow it here: scriptPubKey, amount=ver_amt, witness=witness)
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""])
if sig_good: if sig_good:
jlog.debug('found good sig at index=%d' % (u[0])) 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 # there is an assumption of p2sh-p2wpkh or p2wpkh, for the segwit
# case. # case.
self.latest_tx.vin[u[0]].scriptSig = scriptSig self.latest_tx.vin[u[0]].scriptSig = scriptSig
if ver_amt: if is_witness_input:
self.latest_tx.wit.vtxinwit[u[0]] = btc.CTxInWitness( self.latest_tx.wit.vtxinwit[u[0]] = btc.CTxInWitness(
btc.CScriptWitness(witness)) btc.CScriptWitness(witness))
inserted_sig = True inserted_sig = True
@ -647,8 +643,9 @@ class Taker(object):
# other guy sent a failed signature # other guy sent a failed signature
tx_signed = True tx_signed = True
for ins in self.latest_tx.vin: for input, witness in zip(self.latest_tx.vin, self.latest_tx.wit.vtxinwit):
if ins.scriptSig == b"": if input.scriptSig == b"" \
and witness == btc.CTxInWitness(btc.CScriptWitness([])):
tx_signed = False tx_signed = False
if not tx_signed: if not tx_signed:
return False return False
@ -774,7 +771,8 @@ class Taker(object):
utxo = (ins.prevout.hash[::-1], ins.prevout.n) utxo = (ins.prevout.hash[::-1], ins.prevout.n)
if utxo not in self.input_utxos.keys(): if utxo not in self.input_utxos.keys():
continue 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'] amount = self.input_utxos[utxo]['value']
our_inputs[index] = (script, amount) our_inputs[index] = (script, amount)
success, msg = self.wallet_service.sign_tx(self.latest_tx, our_inputs) 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 jmbase import get_log, hextobin
from jmclient import load_test_config, jm_single,\ from jmclient import load_test_config, jm_single,\
YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet,\ YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet, SegwitWallet,\
NO_ROUNDING NO_ROUNDING
from jmclient.podle import set_commitment_file from jmclient.podle import set_commitment_file
from commontest import make_wallets, default_max_cj_fee 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 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 test_simple_coinjoin(monkeypatch, tmpdir, setup_cj, wallet_cls):
def raise_exit(i): def raise_exit(i):
raise Exception("sys.exit called") 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, res = bitcoin.verify_tx_input(tx2, 0, scriptSig,
bitcoin.pubkey_to_p2sh_p2wpkh_script(pub), bitcoin.pubkey_to_p2sh_p2wpkh_script(pub),
amount = bitcoin.coins_to_satoshi(1), amount = bitcoin.coins_to_satoshi(1),
witness = bitcoin.CScript([sig, pub])) witness = bitcoin.CScriptWitness([sig, pub]))
assert res assert res
def test_absurd_fees(setup_tx_creation): def test_absurd_fees(setup_tx_creation):

Loading…
Cancel
Save