Browse Source

implement coinjoin with differing address types

master
undeath 7 years ago
parent
commit
04aeb38435
  1. 66
      jmclient/jmclient/cryptoengine.py
  2. 11
      jmclient/jmclient/maker.py
  3. 21
      jmclient/jmclient/taker.py
  4. 20
      jmclient/jmclient/wallet.py
  5. 11
      jmclient/test/commontest.py
  6. 11
      jmclient/test/test_taker.py

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

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

21
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

20
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,\

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

11
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):

Loading…
Cancel
Save