Browse Source

Merge #99: add support for non-segwit maker

605eebe add test cases for maker::verify_unsigned_tx (undeath)
d2196ad fix maker crash when verifying transaction with p2sh output in non-sw mode (undeath)
715985d add support for non-segwit maker (undeath)
master
AdamISZ 8 years ago
parent
commit
966bce19a4
No known key found for this signature in database
GPG Key ID: B3AE09F1E9A3197A
  1. 2
      jmclient/jmclient/__init__.py
  2. 10
      jmclient/jmclient/maker.py
  3. 34
      jmclient/jmclient/wallet.py
  4. 16
      jmclient/jmclient/wallet_utils.py
  5. 20
      jmclient/jmclient/yieldgenerator.py
  6. 183
      jmclient/test/test_maker.py

2
jmclient/jmclient/__init__.py

@ -18,7 +18,7 @@ from .slowaes import decryptData, encryptData
from .taker import Taker
from .wallet import (AbstractWallet, BitcoinCoreInterface, Wallet,
BitcoinCoreWallet, estimate_tx_fee, WalletError,
create_wallet_file, SegwitWallet, Bip39Wallet)
create_wallet_file, SegwitWallet, Bip39Wallet, get_wallet_cls)
from .configure import (load_program_config, get_p2pk_vbyte,
jm_single, get_network, validate_address, get_irc_mchannels,
get_blockchain_interface_instance, get_p2sh_vbyte, set_config)

10
jmclient/jmclient/maker.py

@ -69,8 +69,7 @@ class Maker(object):
if res[0]['value'] < reqd_amt:
reason = "commitment utxo too small: " + str(res[0]['value'])
return reject(reason)
if res[0]['address'] != btc.pubkey_to_p2sh_p2wpkh_address(cr_dict['P'],
self.wallet.get_vbyte()):
if res[0]['address'] != self.wallet.pubkey_to_address(cr_dict['P']):
reason = "Invalid podle pubkey: " + str(cr_dict['P'])
return reject(reason)
@ -129,7 +128,9 @@ class Maker(object):
utxos = offerinfo["utxos"]
cjaddr = offerinfo["cjaddr"]
cjaddr_script = btc.address_to_script(cjaddr)
changeaddr = offerinfo["changeaddr"]
changeaddr_script = btc.address_to_script(changeaddr)
amount = offerinfo["amount"]
cjfee = offerinfo["offer"]["cjfee"]
txfee = offerinfo["offer"]["txfee"]
@ -147,12 +148,11 @@ class Maker(object):
times_seen_cj_addr = 0
times_seen_change_addr = 0
for outs in txd['outs']:
addr = btc.script_to_address(outs['script'], get_p2sh_vbyte())
if addr == cjaddr:
if outs['script'] == cjaddr_script:
times_seen_cj_addr += 1
if outs['value'] != amount:
return (False, 'Wrong cj_amount. I expect ' + str(amount))
if addr == changeaddr:
if outs['script'] == changeaddr_script:
times_seen_change_addr += 1
if outs['value'] != expected_change_value:
return (False, 'wrong change, i expect ' + str(

34
jmclient/jmclient/wallet.py

@ -101,6 +101,10 @@ class AbstractWallet(object):
"""
return None
@classmethod
def pubkey_to_address(cls, pubkey):
return None
def update_cache_index(self):
pass
@ -214,12 +218,17 @@ class Wallet(AbstractWallet):
"""
return btc.sign(tx, i, priv)
def script_to_address(self, script):
@classmethod
def script_to_address(cls, script):
"""Return the address for a given output script,
which will be p2pkh for the default Wallet object,
and reading the correct network byte from the config.
"""
return btc.script_to_address(script, get_p2pk_vbyte())
return btc.script_to_address(script, cls.get_vbyte())
@classmethod
def pubkey_to_address(cls, pubkey):
return btc.pubkey_to_address(pubkey, cls.get_vbyte())
def read_wallet_file_data(self, filename, pwd=None, wallet_dir=None):
self.path = None
@ -385,8 +394,8 @@ class Wallet(AbstractWallet):
self.spent_utxos += removed_utxos.keys()
return removed_utxos
def get_vbyte(self):
@staticmethod
def get_vbyte():
return get_p2pk_vbyte()
def add_new_utxos(self, tx, txid):
@ -467,7 +476,8 @@ class SegwitWallet(Bip39Wallet):
root = btc.bip32_ckd(pre_root, testnet_flag + 2**31)
return [btc.bip32_ckd(root, c + 2**31) for c in range(self.max_mix_depth)]
def get_vbyte(self):
@staticmethod
def get_vbyte():
return get_p2sh_vbyte()
def get_txtype(self):
@ -484,13 +494,18 @@ class SegwitWallet(Bip39Wallet):
pub = btc.privtopub(self.get_key(mixing_depth, forchange, i))
return btc.pubkey_to_p2sh_p2wpkh_address(pub, magicbyte=self.get_vbyte())
def script_to_address(self, script):
@classmethod
def script_to_address(cls, script):
"""Return the address for a given output script,
which will be p2sh-p2wpkh for the segwit (currently).
The underlying witness is however invisible at this layer;
so it's just a p2sh address.
"""
return btc.script_to_address(script, get_p2sh_vbyte())
return btc.script_to_address(script, cls.get_vbyte())
@classmethod
def pubkey_to_address(cls, pubkey):
return btc.pubkey_to_p2sh_p2wpkh_address(pubkey, cls.get_vbyte())
def sign(self, tx, i, priv, amount):
"""Sign a transaction; the amount field
@ -551,3 +566,8 @@ class BitcoinCoreWallet(AbstractWallet): #pragma: no cover
if exc.code != -14:
raise exc
# Wrong passphrase, try again.
def get_wallet_cls():
if jm_single().config.get("POLICY", "segwit") == "true":
return SegwitWallet
return Wallet

16
jmclient/jmclient/wallet_utils.py

@ -9,10 +9,10 @@ from datetime import datetime
from mnemonic import Mnemonic
from optparse import OptionParser
import getpass
from jmclient import (get_network, Wallet, Bip39Wallet, podle,
from jmclient import (get_network, get_wallet_cls, Bip39Wallet, podle,
encryptData, get_p2sh_vbyte, get_p2pk_vbyte, jm_single,
mn_decode, mn_encode, BitcoinCoreInterface,
JsonRpcError, sync_wallet, WalletError, SegwitWallet)
JsonRpcError, sync_wallet, WalletError)
from jmbase.support import get_password
import jmclient.btc as btc
@ -827,8 +827,6 @@ def wallet_tool_main(wallet_root_path):
"""
parser = get_wallettool_parser()
(options, args) = parser.parse_args()
walletclass = SegwitWallet if jm_single().config.get(
"POLICY", "segwit") == "true" else Wallet
# if the index_cache stored in wallet.json is longer than the default
# then set maxmixdepth to the length of index_cache
maxmixdepth_configured = True
@ -852,15 +850,15 @@ def wallet_tool_main(wallet_root_path):
seed = args[0]
method = ('display' if len(args) == 1 else args[1].lower())
if not os.path.exists(os.path.join(wallet_root_path, seed)):
wallet = walletclass(seed, None, options.maxmixdepth,
options.gaplimit, extend_mixdepth= not maxmixdepth_configured,
storepassword=(method == 'importprivkey'),
wallet_dir=wallet_root_path)
wallet = get_wallet_cls()(seed, None, options.maxmixdepth,
options.gaplimit, extend_mixdepth= not maxmixdepth_configured,
storepassword=(method == 'importprivkey'),
wallet_dir=wallet_root_path)
else:
while True:
try:
pwd = get_password("Enter wallet decryption passphrase: ")
wallet = walletclass(seed, pwd,
wallet = get_wallet_cls()(seed, pwd,
options.maxmixdepth,
options.gaplimit,
extend_mixdepth=not maxmixdepth_configured,

20
jmclient/jmclient/yieldgenerator.py

@ -9,7 +9,7 @@ from twisted.python.log import startLogging
from optparse import OptionParser
from jmbase import get_password
from jmclient import (Maker, jm_single, get_network, load_program_config, get_log,
SegwitWallet, sync_wallet, JMClientProtocolFactory,
get_wallet_cls, sync_wallet, JMClientProtocolFactory,
start_reactor, calc_cj_fee, WalletError)
jlog = get_log()
@ -87,13 +87,13 @@ class YieldGeneratorBasic(YieldGenerator):
# print mix_balance
max_mix = max(mix_balance, key=mix_balance.get)
f = '0'
if self.ordertype == 'swreloffer':
if self.ordertype in ('reloffer', 'swreloffer'):
f = self.cjfee_r
#minimum size bumped if necessary such that you always profit
#least 50% of the miner fee
self.minsize = max(int(1.5 * self.txfee / float(self.cjfee_r)),
self.minsize)
elif self.ordertype == 'swabsoffer':
elif self.ordertype in ('absoffer', 'swabsoffer'):
f = str(self.txfee + self.cjfee_a)
order = {'oid': 0,
'ordertype': self.ordertype,
@ -215,13 +215,13 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffe
wallet_name = args[0]
ordertype = options.ordertype
txfee = options.txfee
if ordertype == 'swreloffer':
if ordertype in ('reloffer', 'swreloffer'):
if options.cjfee != '':
cjfee_r = options.cjfee
# minimum size is such that you always net profit at least 20%
#of the miner fee
minsize = max(int(1.2 * txfee / float(cjfee_r)), options.minsize)
elif ordertype == 'swabsoffer':
elif ordertype in ('absoffer', 'swabsoffer'):
if options.cjfee != '':
cjfee_a = int(options.cjfee)
minsize = options.minsize
@ -233,15 +233,15 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffe
load_program_config()
if not os.path.exists(os.path.join('wallets', wallet_name)):
wallet = SegwitWallet(wallet_name, None, max_mix_depth=MAX_MIX_DEPTH,
gaplimit=options.gaplimit)
wallet = get_wallet_cls()(wallet_name, None, max_mix_depth=MAX_MIX_DEPTH,
gaplimit=options.gaplimit)
else:
while True:
try:
pwd = get_password("Enter wallet decryption passphrase: ")
wallet = SegwitWallet(wallet_name, pwd,
max_mix_depth=MAX_MIX_DEPTH,
gaplimit=options.gaplimit)
wallet = get_wallet_cls()(wallet_name, pwd,
max_mix_depth=MAX_MIX_DEPTH,
gaplimit=options.gaplimit)
except WalletError:
print("Wrong password, try again.")
continue

183
jmclient/test/test_maker.py

@ -0,0 +1,183 @@
#!/usr/bin/env python
from __future__ import print_function
from jmclient import AbstractWallet, Maker, btc, get_p2sh_vbyte, get_p2pk_vbyte, \
load_program_config, jm_single
import jmclient
from commontest import DummyBlockchainInterface
import struct
import binascii
from itertools import chain
import pytest
class MockWallet(AbstractWallet):
pass
class OfflineMaker(Maker):
def try_to_create_my_orders(self):
self.sync_wait_loop.stop()
def construct_tx_offerlist(cjaddr, changeaddr, maker_utxos, maker_utxos_value,
cj_value, ordertype):
offer = {
'cjfee': '0',
'maxsize': cj_value*3,
'minsize': 7500000,
'oid': 0,
'ordertype': ordertype,
'txfee': 0
}
utxos = { utxo['outpoint']['hash'] + ':' + str(utxo['outpoint']['index']):
{'utxo': utxo, 'value': maker_utxos_value} for utxo in maker_utxos }
offerlist = {
'utxos': utxos,
'cjaddr': cjaddr,
'changeaddr': changeaddr,
'amount': cj_value,
'offer': offer
}
return offerlist
def create_tx_inputs(count=1):
inp = []
for i in xrange(count):
inp.append({'outpoint': {'hash': '0'*64, 'index': i},
'script': '',
'sequence': 4294967295})
return inp
def create_tx_outputs(*scripts_amount):
outp = []
for script, amount in scripts_amount:
outp.append({'script': script, 'value': amount})
return outp
def address_p2pkh_generator():
return get_address_generator(b'\x76\xa9\x14', b'\x88\xac', get_p2pk_vbyte())
def address_p2sh_generator():
return get_address_generator(b'\xa9\x14', b'\x87', get_p2sh_vbyte())
def get_address_generator(script_pre, script_post, vbyte):
counter = 0
while True:
script = script_pre + struct.pack('=LQQ', 0, 0, counter) + script_post
addr = btc.script_to_address(script, vbyte)
yield addr, binascii.hexlify(script)
counter += 1
def create_tx_and_offerlist(cj_addr, changeaddr, other_output_scripts,
cj_script=None, cj_change_script=None, offertype='swreloffer'):
assert len(other_output_scripts) % 2 == 0, "bug in test"
other_participant_count = len(other_output_scripts) // 2
cj_value = 100000000
maker_total_value = cj_value*3
if cj_script is None:
cj_script = btc.address_to_script(cj_addr)
if cj_change_script is None:
changeaddr = btc.address_to_script(cj_change_addr)
inputs = create_tx_inputs(3)
outputs = create_tx_outputs(
(cj_script, cj_value),
(cj_change_script, maker_total_value - cj_value), # cjfee=0, txfee=0
*((script, cj_value + (i%2)*(50000000+i)) \
for i, script in enumerate(other_output_scripts))
)
maker_utxos = [inputs[0]]
tx = btc.deserialize(btc.mktx(inputs, outputs))
offerlist = construct_tx_offerlist(cj_addr, changeaddr, maker_utxos,
maker_total_value, cj_value, offertype)
return tx, offerlist
def test_verify_unsigned_tx_sw_valid(setup_env_nodeps):
jm_single().config.set("POLICY", "segwit", "true")
p2sh_gen = address_p2sh_generator()
p2pkh_gen = address_p2pkh_generator()
wallet = MockWallet()
maker = OfflineMaker(wallet)
cj_addr, cj_script = next(p2sh_gen)
changeaddr, cj_change_script = next(p2sh_gen)
# test standard cj
tx, offerlist = create_tx_and_offerlist(cj_addr, changeaddr,
[next(p2sh_gen)[1] for s in xrange(4)], cj_script, cj_change_script)
assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "standard sw cj"
# test cj with mixed outputs
tx, offerlist = create_tx_and_offerlist(cj_addr, changeaddr,
list(chain((next(p2sh_gen)[1] for s in xrange(3)),
(next(p2pkh_gen)[1] for s in xrange(1)))),
cj_script, cj_change_script)
assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "sw cj with p2pkh output"
# test cj with only p2pkh outputs
tx, offerlist = create_tx_and_offerlist(cj_addr, changeaddr,
[next(p2pkh_gen)[1] for s in xrange(4)], cj_script, cj_change_script)
assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "sw cj with only p2pkh outputs"
def test_verify_unsigned_tx_nonsw_valid(setup_env_nodeps):
jm_single().config.set("POLICY", "segwit", "false")
p2sh_gen = address_p2sh_generator()
p2pkh_gen = address_p2pkh_generator()
wallet = MockWallet()
maker = OfflineMaker(wallet)
cj_addr, cj_script = next(p2pkh_gen)
changeaddr, cj_change_script = next(p2pkh_gen)
# test standard cj
tx, offerlist = create_tx_and_offerlist(cj_addr, changeaddr,
[next(p2pkh_gen)[1] for s in xrange(4)], cj_script, cj_change_script, 'reloffer')
assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "standard nonsw cj"
# test cj with mixed outputs
tx, offerlist = create_tx_and_offerlist(cj_addr, changeaddr,
list(chain((next(p2sh_gen)[1] for s in xrange(1)),
(next(p2pkh_gen)[1] for s in xrange(3)))),
cj_script, cj_change_script, 'reloffer')
assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "nonsw cj with p2sh output"
# test cj with only p2sh outputs
tx, offerlist = create_tx_and_offerlist(cj_addr, changeaddr,
[next(p2sh_gen)[1] for s in xrange(4)], cj_script, cj_change_script, 'reloffer')
assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "nonsw cj with only p2sh outputs"
@pytest.fixture
def setup_env_nodeps(monkeypatch):
monkeypatch.setattr(jmclient.configure, 'get_blockchain_interface_instance',
lambda x: DummyBlockchainInterface())
load_program_config()
Loading…
Cancel
Save