diff --git a/jmclient/jmclient/cryptoengine.py b/jmclient/jmclient/cryptoengine.py index baa3375..a2ddd6d 100644 --- a/jmclient/jmclient/cryptoengine.py +++ b/jmclient/jmclient/cryptoengine.py @@ -54,6 +54,21 @@ def pubkey_to_p2wpkh_script(pubkey): return _pubkey_to_script(pubkey, P2WPKH_PRE) +def detect_script_type(script): + if script.startswith(P2PKH_PRE) and script.endswith(P2PKH_POST) and\ + len(script) == 0x14 + len(P2PKH_PRE) + len(P2PKH_POST): + return TYPE_P2PKH + elif (script.startswith(P2SH_P2WPKH_PRE) and + script.endswith(P2SH_P2WPKH_POST) and + len(script) == 0x14 + len(P2SH_P2WPKH_PRE) + len(P2SH_P2WPKH_POST)): + return TYPE_P2SH_P2WPKH + elif script.startswith(P2WPKH_PRE) and\ + len(script) == 0x14 + len(P2WPKH_PRE): + return TYPE_P2WPKH + raise EngineError("Unknown script type for script '{}'" + .format(hexlify(script))) + + class classproperty(object): """ from https://stackoverflow.com/a/5192374 @@ -192,6 +207,19 @@ class BTCEngine(object): script = cls.pubkey_to_script(pubkey) return btc.script_to_address(script, cls.VBYTE) + @classmethod + def pubkey_has_address(cls, pubkey, addr): + ascript = cls.address_to_script(addr) + return cls.pubkey_has_script(pubkey, ascript) + + @classmethod + def pubkey_has_script(cls, pubkey, script): + stype = detect_script_type(script) + assert stype in ENGINES + engine = ENGINES[stype] + pscript = engine.pubkey_to_script(pubkey) + return script == pscript + @classmethod def sign_transaction(cls, tx, index, privkey, amount): raise NotImplementedError() @@ -272,3 +300,41 @@ class BTC_P2SH_P2WPKH(BTCEngine): tx['ins'][index]['txinwitness'] = [sig, pubkey] return tx + + +class BTC_P2WPKH(BTCEngine): + @classproperty + def VBYTE(cls): + return btc.BTC_P2SH_VBYTE[get_network()] + + @classmethod + def pubkey_to_script(cls, pubkey): + return pubkey_to_p2wpkh_script(pubkey) + + @classmethod + def sign_transaction(cls, tx, index, privkey, amount, + hashcode=btc.SIGHASH_ALL, **kwargs): + assert amount is not None + raise NotImplementedError("The following code is completely untested") + + pubkey = cls.privkey_to_pubkey(privkey) + script = cls.pubkey_to_script(pubkey) + + signing_tx = btc.segwit_signature_form(tx, index, script, amount, + hashcode=hashcode, + decoder_func=lambda x: x) + # FIXME: encoding mess + sig = unhexlify(btc.ecdsa_tx_sign(signing_tx, hexlify(privkey), + hashcode=hashcode, **kwargs)) + + tx['ins'][index]['script'] = script + tx['ins'][index]['txinwitness'] = [sig, pubkey] + + return tx + + +ENGINES = { + TYPE_P2PKH: BTC_P2PKH, + TYPE_P2SH_P2WPKH: BTC_P2SH_P2WPKH, + TYPE_P2WPKH: BTC_P2WPKH +} diff --git a/jmclient/jmclient/maker.py b/jmclient/jmclient/maker.py index e7c56cf..c143c4d 100644 --- a/jmclient/jmclient/maker.py +++ b/jmclient/jmclient/maker.py @@ -15,6 +15,8 @@ from jmbase.support import get_log from jmclient.support import (calc_cj_fee) from jmclient.podle import verify_podle, PoDLE, PoDLEError from twisted.internet import task +from .cryptoengine import EngineError + jlog = get_log() class Maker(object): @@ -81,10 +83,11 @@ class Maker(object): reason = "commitment utxo too small: " + str(res[0]['value']) return reject(reason) - # FIXME: This only works if taker's commitment address is of same type - # as our wallet. - if res[0]['address'] != \ - self.wallet.pubkey_to_addr(unhexlify(cr_dict['P'])): + try: + if not self.wallet.pubkey_has_script( + unhexlify(cr_dict['P']), unhexlify(res[0]['script'])): + raise EngineError() + except EngineError: reason = "Invalid podle pubkey: " + str(cr_dict['P']) return reject(reason) diff --git a/jmclient/jmclient/taker.py b/jmclient/jmclient/taker.py index 0f13ed1..d74d11c 100644 --- a/jmclient/jmclient/taker.py +++ b/jmclient/jmclient/taker.py @@ -17,6 +17,9 @@ from jmclient.support import (calc_cj_fee, weighted_order_choose, choose_orders, from jmclient.wallet import estimate_tx_fee from jmclient.podle import generate_podle, get_podle_commitments, PoDLE from .output import generate_podle_error_string +from .cryptoengine import EngineError + + jlog = get_log() @@ -377,14 +380,18 @@ class Taker(object): #Extract the address fields from the utxos #Construct the Bitcoin address for the auth_pub field #Ensure that at least one address from utxos corresponds. - input_addresses = [d['address'] for d in utxo_data] - # FIXME: This only works if taker's commitment address is of same type - # as our wallet. - auth_address = self.wallet.pubkey_to_addr(unhexlify(auth_pub)) - if not auth_address in input_addresses: + auth_pub_bin = unhexlify(auth_pub) + for inp in utxo_data: + try: + if self.wallet.pubkey_has_script( + auth_pub_bin, unhexlify(inp['script'])): + break + except EngineError: + pass + else: jlog.warn("ERROR maker's (" + nick + ")" - " authorising pubkey is not included " - "in the transaction: " + str(auth_address)) + " authorising pubkey is not included " + "in the transaction!") #this will not be added to the transaction, so we will have #to recheck if we have enough continue diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 776eaf5..5d9d1ce 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -20,8 +20,7 @@ from numbers import Integral from .configure import jm_single from .support import select_gradual, select_greedy, select_greediest, \ select -from .cryptoengine import BTC_P2PKH, BTC_P2SH_P2WPKH, TYPE_P2PKH, \ - TYPE_P2SH_P2WPKH +from .cryptoengine import TYPE_P2PKH, TYPE_P2SH_P2WPKH, ENGINES from .support import get_random_bytes from . import mn_encode, mn_decode, btc @@ -204,10 +203,7 @@ class BaseWallet(object): 'greediest': select_greediest } - _ENGINES = { - TYPE_P2PKH: BTC_P2PKH, - TYPE_P2SH_P2WPKH: BTC_P2SH_P2WPKH - } + _ENGINES = ENGINES _ENGINE = None @@ -384,6 +380,14 @@ class BaseWallet(object): engine = self._get_priv_from_path(path)[1] return engine.script_to_address(script) + @classmethod + def pubkey_has_address(cls, pubkey, addr): + return cls._ENGINE.pubkey_has_address(pubkey, addr) + + @classmethod + def pubkey_has_script(cls, pubkey, script): + return cls._ENGINE.pubkey_has_script(pubkey, script) + @deprecated def get_key(self, mixdepth, internal, index): raise NotImplementedError() @@ -1340,7 +1344,7 @@ class BIP32Wallet(BaseWallet): class LegacyWallet(ImportWalletMixin, BIP32Wallet): TYPE = TYPE_P2PKH - _ENGINE = BTC_P2PKH + _ENGINE = ENGINES[TYPE_P2PKH] def _create_master_key(self): return hexlify(self._entropy) @@ -1351,7 +1355,7 @@ class LegacyWallet(ImportWalletMixin, BIP32Wallet): class BIP49Wallet(BIP32Wallet): _BIP49_PURPOSE = 2**31 + 49 - _ENGINE = BTC_P2SH_P2WPKH + _ENGINE = ENGINES[TYPE_P2SH_P2WPKH] def _get_bip32_base_path(self): return self._key_ident, self._BIP49_PURPOSE,\ diff --git a/jmclient/test/commontest.py b/jmclient/test/commontest.py index 68d5888..2fa123a 100644 --- a/jmclient/test/commontest.py +++ b/jmclient/test/commontest.py @@ -91,13 +91,16 @@ class DummyBlockchainInterface(BlockchainInterface): 'confirms': wallet_outs[to][1]}) return results if txouts[0] in known_outs: + addr = btc.pubkey_to_p2sh_p2wpkh_address( + known_outs[txouts[0]], get_p2sh_vbyte()) return [{'value': 200000000, - 'address': btc.pubkey_to_p2sh_p2wpkh_address( - known_outs[txouts[0]], get_p2sh_vbyte()), - 'confirms': 20}] + 'address': addr, + 'script': btc.address_to_script(addr), + 'confirms': 20}] for t in txouts: result_dict = {'value': 10000000000, - 'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ"} + 'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ", + 'script': '76a91479b000887626b294a914501a4cd226b58b23598388ac'} if includeconf: result_dict['confirms'] = 20 result.append(result_dict) diff --git a/jmclient/test/test_taker.py b/jmclient/test/test_taker.py index 8fac8f2..1a6a69f 100644 --- a/jmclient/test/test_taker.py +++ b/jmclient/test/test_taker.py @@ -197,9 +197,10 @@ def test_auth_pub_not_found(createcmtdata): "498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0", "3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1"] fake_query_results = [{'value': 200000000, - 'address': "blah", - 'utxo': utxos[i], - 'confirms': 20} for i in range(3)] + 'address': "mrKTGvFfYUEqk52qPKUroumZJcpjHLQ6pn", + 'script': '76a914767c956efe6092a775fea39a06d1cac9aae956d788ac', + 'utxo': utxos[i], + 'confirms': 20} for i in range(3)] jm_single().bc_interface.insert_fake_query_results(fake_query_results) res = taker.receive_utxos(maker_response) assert not res[0] @@ -418,9 +419,13 @@ def test_on_sig(createcmtdata, dummyaddr, schedule): #my inputs are the first 2 utxos taker.input_utxos = {utxos[0]: {'address': bitcoin.privkey_to_address(privs[0], False, magicbyte=0x6f), + 'script': bitcoin.mk_pubkey_script( + bitcoin.privkey_to_address(privs[0], False, magicbyte=0x6f)), 'value': 200000000}, utxos[1]: {'address': bitcoin.privkey_to_address(privs[1], False, magicbyte=0x6f), + 'script': bitcoin.mk_pubkey_script( + bitcoin.privkey_to_address(privs[1], False, magicbyte=0x6f)), 'value': 200000000}} taker.utxos = {None: utxos[:2], "cp1": [utxos[2]], "cp2": [utxos[3]], "cp3":[utxos[4]]} for i in range(2):