From 73cdad08dd79139118d43f7f7319a2c3ee042466 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Mon, 26 Jun 2017 20:26:47 +0300 Subject: [PATCH] initial sw-wallet support --- jmbitcoin/jmbitcoin/secp256k1_main.py | 10 ++++ jmclient/jmclient/wallet.py | 86 ++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 7 deletions(-) diff --git a/jmbitcoin/jmbitcoin/secp256k1_main.py b/jmbitcoin/jmbitcoin/secp256k1_main.py index 3886830..fbb9e4c 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_main.py +++ b/jmbitcoin/jmbitcoin/secp256k1_main.py @@ -443,6 +443,16 @@ def estimate_tx_size(ins, outs, txtype='p2pkh'): ''' if txtype == 'p2pkh': return 10 + ins * 147 + 34 * outs + elif txtype == 'p2sh-p2wpkh': + #return the estimate for the witness and non-witness + #portions of the transaction, assuming that all the inputs + #are of segwit type p2sh-p2wpkh + #witness are roughly 3+~73+33 for each input + #non-witness input fields are roughly 32+4+4+20+4=64, so total becomes + #n_in * 64 + 4(ver) + 4(locktime) + n_out*34 + n_in * 109 + witness_estimate = ins*109 + non_witness_estimate = 4 + 4 + outs*34 + ins*64 + return (witness_estimate, non_witness_estimate) elif txtype == 'p2shMofN': ins, M, N = ins return 10 + (45 + 74*M + 34*N) * ins + 34 * outs diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 912a8e6..43c45a0 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -18,6 +18,9 @@ from jmclient.support import select_gradual, select_greedy,select_greediest, sel log = get_log() +JM_WALLET_P2PKH = "00" +JM_WALLET_SW_P2SH_P2WPKH = "01" + class WalletError(Exception): pass @@ -26,17 +29,22 @@ def estimate_tx_fee(ins, outs, txtype='p2pkh'): for a transaction with the given number of inputs and outputs, based on information from the blockchain interface. ''' - tx_estimated_bytes = btc.estimate_tx_size(ins, outs, txtype) - log.debug("Estimated transaction size: "+str(tx_estimated_bytes)) fee_per_kb = jm_single().bc_interface.estimate_fee_per_kb( - jm_single().config.getint("POLICY", "tx_fees")) + jm_single().config.getint("POLICY","tx_fees")) absurd_fee = jm_single().config.getint("POLICY", "absurd_fee_per_kb") if fee_per_kb > absurd_fee: #This error is considered critical; for safety reasons, shut down. raise ValueError("Estimated fee per kB greater than absurd value: " + \ - str(absurd_fee) + ", quitting.") - log.debug("got estimated tx bytes: "+str(tx_estimated_bytes)) - return int((tx_estimated_bytes * fee_per_kb)/Decimal(1000.0)) + str(absurd_fee) + ", quitting.") + if txtype=='p2pkh': + tx_estimated_bytes = btc.estimate_tx_size(ins, outs, txtype) + log.debug("Estimated transaction size: "+str(tx_estimated_bytes)) + return int((tx_estimated_bytes * fee_per_kb)/Decimal(1000.0)) + elif txtype=='p2sh-p2wpkh': + witness_estimate, non_witness_estimate = btc.estimate_tx_size( + ins, outs, 'p2sh-p2wpkh') + return int(int(( + non_witness_estimate + 0.25*witness_estimate)*fee_per_kb)/Decimal(1000.0)) def create_wallet_file(pwd, seed): password_key = btc.bin_dbl_sha256(pwd) @@ -134,6 +142,7 @@ class Wallet(AbstractWallet): storepassword=False, wallet_dir=None): super(Wallet, self).__init__() + self.vflag = JM_WALLET_P2PKH self.max_mix_depth = max_mix_depth self.storepassword = storepassword # key is address, value is (mixdepth, forchange, index) if mixdepth = @@ -161,6 +170,26 @@ class Wallet(AbstractWallet): for i in range(self.max_mix_depth): self.index.append([0, 0]) + def get_txtype(self): + """Return string defining wallet type + for purposes of transaction size estimates + """ + return 'p2pkh' + + def sign(self, tx, i, priv, amount): + """Sign a transaction for pushing + onto the network. The amount field + is not used in this case (p2pkh) + """ + return btc.sign(tx, i, priv) + + def script_to_address(self, 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()) + def read_wallet_file_data(self, filename, pwd=None, wallet_dir=None): self.path = None wallet_dir = wallet_dir if wallet_dir else 'wallets' @@ -299,10 +328,14 @@ class Wallet(AbstractWallet): self.spent_utxos += removed_utxos.keys() return removed_utxos + + def get_vbyte(self): + return get_p2pk_vbyte() + def add_new_utxos(self, tx, txid): added_utxos = {} for index, outs in enumerate(tx['outs']): - addr = btc.script_to_address(outs['script'], get_p2pk_vbyte()) + addr = btc.script_to_address(outs['script'], self.get_vbyte()) if addr not in self.addr_cache: continue addrdict = {'address': addr, 'value': outs['value']} @@ -329,6 +362,45 @@ class Wallet(AbstractWallet): log.debug('get_utxos_by_mixdepth = \n' + pprint.pformat(mix_utxo_list)) return mix_utxo_list +class SegwitWallet(Wallet): + + def __init__(self, seedarg, max_mix_depth=2, gaplimit=6, + extend_mixdepth=False, storepassword=False): + super(SegwitWallet, self).__init__(seedarg, max_mix_depth, gaplimit, + extend_mixdepth, storepassword) + self.vflag = JM_WALLET_SW_P2SH_P2WPKH + + def get_vbyte(self): + return get_p2sh_vbyte() + + def get_txtype(self): + """Return string defining wallet type + for purposes of transaction size estimates + """ + return 'p2sh-p2wpkh' + + def get_addr(self, mixing_depth, forchange, i): + """Construct a p2sh-p2wpkh style address for the + keypair corresponding to mixing depth mixing_depth, + branch forchange and index i + """ + 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): + """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()) + + def sign(self, tx, i, priv, amount): + """Sign a transaction; the amount field + triggers the segwit style signing. + """ + log.debug("About to sign for this amount: " + str(amount)) + return btc.sign(tx, i, priv, amount=amount) class BitcoinCoreWallet(AbstractWallet): #pragma: no cover def __init__(self, fromaccount):