diff --git a/jmbitcoin/jmbitcoin/secp256k1_transaction.py b/jmbitcoin/jmbitcoin/secp256k1_transaction.py index 91bb4f5..a59e3d3 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_transaction.py +++ b/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, diff --git a/jmclient/jmclient/maker.py b/jmclient/jmclient/maker.py index 631a511..934f89a 100644 --- a/jmclient/jmclient/maker.py +++ b/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) diff --git a/jmclient/jmclient/taker.py b/jmclient/jmclient/taker.py index 3669cd6..5a60d04 100644 --- a/jmclient/jmclient/taker.py +++ b/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) diff --git a/jmclient/test/test_coinjoin.py b/jmclient/test/test_coinjoin.py index 0c851b8..da3db93 100644 --- a/jmclient/test/test_coinjoin.py +++ b/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") diff --git a/jmclient/test/test_tx_creation.py b/jmclient/test/test_tx_creation.py index 4c0aafe..de13f18 100644 --- a/jmclient/test/test_tx_creation.py +++ b/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):