From d9f2edf6b0c8d8e1b5219bfa5cfeac903b6a2561 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 1 Sep 2017 14:15:54 +0200 Subject: [PATCH] support native segwit transactions --- lib/base_wizard.py | 2 +- lib/bitcoin.py | 17 ++++--- lib/commands.py | 3 +- lib/keystore.py | 2 + lib/transaction.py | 119 +++++++++++++++++++++++++++++++-------------- lib/wallet.py | 38 ++++++++------- 6 files changed, 120 insertions(+), 61 deletions(-) diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 6efaf481f..b4646dcb5 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -357,7 +357,7 @@ class BaseWizard(object): def create_seed(self): from . import mnemonic - self.seed_type = 'segwit' if bitcoin.TESTNET and self.config.get('segwit') else 'standard' + self.seed_type = 'segwit' if self.config.get('segwit') else 'standard' seed = mnemonic.Mnemonic('en').make_seed(self.seed_type) self.opt_bip39 = False f = lambda x: self.request_passphrase(seed, x) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 76370e78f..027c573e3 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -251,7 +251,7 @@ def seed_type(x): return 'old' elif is_new_seed(x): return 'standard' - elif TESTNET and is_new_seed(x, version.SEED_PREFIX_SW): + elif is_new_seed(x, version.SEED_PREFIX_SW): return 'segwit' elif is_new_seed(x, version.SEED_PREFIX_2FA): return '2fa' @@ -307,16 +307,21 @@ def b58_address_to_hash160(addr): def hash160_to_p2pkh(h160): return hash160_to_b58_address(h160, ADDRTYPE_P2PKH) - def hash160_to_p2sh(h160): return hash160_to_b58_address(h160, ADDRTYPE_P2SH) - def public_key_to_p2pkh(public_key): return hash160_to_p2pkh(hash_160(public_key)) -def hash160_to_segwit_addr(h160): - return segwit_addr.encode(SEGWIT_HRP, 0, h160) +def hash_to_segwit_addr(h): + return segwit_addr.encode(SEGWIT_HRP, 0, h) + +def public_key_to_p2wpkh(public_key): + return hash_to_segwit_addr(hash_160(public_key)) + +def script_to_p2wsh(script): + return hash_to_segwit_addr(sha256(bfh(script))) + def address_to_script(addr): if is_segwit_address(addr): @@ -838,7 +843,7 @@ def deserialize_xkey(xkey, prv): c = xkey[13:13+32] header = XPRV_HEADER if prv else XPUB_HEADER xtype = int('0x' + bh2u(xkey[0:4]), 16) - header - if xtype not in ([0, 1] if TESTNET else [0]): + if xtype not in [0, 1]: raise BaseException('Invalid header') n = 33 if prv else 32 K_or_k = xkey[13+n:] diff --git a/lib/commands.py b/lib/commands.py index 02288fbc4..6a871a40e 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -163,7 +163,8 @@ class Commands: def make_seed(self, nbits=132, entropy=1, language=None): """Create a seed""" from .mnemonic import Mnemonic - s = Mnemonic(language).make_seed('standard', nbits, custom_entropy=entropy) + t = 'segwit' if self.config.get('segwit') else 'standard' + s = Mnemonic(language).make_seed(t, nbits, custom_entropy=entropy) return s @command('') diff --git a/lib/keystore.py b/lib/keystore.py index 8d5ac1230..538671342 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -701,6 +701,8 @@ def from_seed(seed, passphrase): bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase) xtype = 0 if t == 'standard' else 1 keystore.add_xprv_from_seed(bip32_seed, xtype, "m/") + else: + raise BaseException(t) return keystore def from_private_key_list(text): diff --git a/lib/transaction.py b/lib/transaction.py index 753ecfa4c..392a015db 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -116,6 +116,19 @@ class BCDataStream(object): def write_int64(self, val): return self._write_num(' 2: + txin['num_sig'] = n - 2 + txin['signatures'] = parse_sig(w[1:-1]) + m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1])) + txin['x_pubkeys'] = x_pubkeys + txin['pubkeys'] = pubkeys + txin['witnessScript'] = witnessScript + else: + txin['num_sig'] = 1 + txin['pubkeys'] = [ w[-1] ] + txin['signatures'] = parse_sig([w[:-1]]) + def parse_output(vds, i): d = {} @@ -451,19 +482,24 @@ def deserialize(raw): n_vin = vds.read_compact_size() d['inputs'] = [parse_input(vds) for i in range(n_vin)] n_vout = vds.read_compact_size() - d['outputs'] = [parse_output(vds,i) for i in range(n_vout)] + d['outputs'] = [parse_output(vds, i) for i in range(n_vout)] if is_segwit: - d['witness'] = [parse_witness(vds) for i in range(n_vin)] + for i in range(n_vin): + txin = d['inputs'][i] + parse_witness(vds, txin) + if not txin.get('scriptSig'): + if txin['num_sig'] == 1: + txin['type'] = 'p2wpkh' + txin['address'] = bitcoin.public_key_to_p2wpkh(bfh(txin['pubkeys'][0])) + else: + txin['type'] = 'p2wsh' + txin['address'] = bitcoin.script_to_p2wsh(txin['witnessScript']) d['lockTime'] = vds.read_uint32() return d # pay & redeem scripts - - - - def segwit_script(pubkey): pubkey = safe_parse_pubkey(pubkey) pkh = bh2u(hash_160(bfh(pubkey))) @@ -536,12 +572,7 @@ class Transaction: for i, txin in enumerate(self.inputs()): pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) sigs1 = txin.get('signatures') - if d.get('witness') is None: - sigs2 = d['inputs'][i].get('signatures') - else: - # signatures are in the witnesses. But the last item is - # the pubkey or the multisig script, so skip that. - sigs2 = d['witness'][i][:-1] + sigs2 = d['inputs'][i].get('signatures') for sig in sigs2: if sig in sigs1: continue @@ -622,12 +653,18 @@ class Transaction: @classmethod def serialize_witness(self, txin): pubkeys, sig_list = self.get_siglist(txin) - n = len(pubkeys) + len(sig_list) - return var_int(n) + ''.join(push_script(x) for x in sig_list) + ''.join(push_script(x) for x in pubkeys) + if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']: + n = 2 + return var_int(n) + push_script(sig_list[0]) + push_script(pubkeys[0]) + elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']: + n = len(sig_list) + 2 + # fixme: witness script must be decided by wallet + witness_script = multisig_script(pubkeys, txin['num_sig']) + return var_int(n) + '00' + ''.join(push_script(x) for x in sig_list) + push_script(witness_script) @classmethod def is_segwit_input(self, txin): - return txin['type'] in ['p2wpkh-p2sh'] + return txin['type'] in ['p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh'] @classmethod def input_script(self, txin, estimate_size=False): @@ -645,7 +682,10 @@ class Transaction: script += push_script(redeem_script) elif _type == 'p2pkh': script += push_script(pubkeys[0]) - elif _type == 'p2wpkh-p2sh': + elif _type in ['p2wpkh', 'p2wsh']: + # if it is not complete we store the value + return '' if self.is_txin_complete(txin) or estimate_size else int_to_hex(txin['value'], 8) + elif _type in ['p2wpkh-p2sh', 'p2wsh-p2sh']: redeem_script = txin.get('redeemScript') or segwit_script(pubkeys[0]) return push_script(redeem_script) elif _type == 'address': @@ -654,15 +694,22 @@ class Transaction: return txin['scriptSig'] return script + @classmethod + def is_txin_complete(self, txin): + num_sig = txin.get('num_sig', 1) + x_signatures = txin['signatures'] + signatures = list(filter(None, x_signatures)) + return len(signatures) == num_sig + @classmethod def get_preimage_script(self, txin): # only for non-segwit if txin['type'] == 'p2pkh': return bitcoin.address_to_script(txin['address']) - elif txin['type'] == 'p2sh': + elif txin['type'] in ['p2sh', 'p2wsh']: pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) return multisig_script(pubkeys, txin['num_sig']) - elif txin['type'] == 'p2wpkh-p2sh': + elif txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']: pubkey = txin['pubkeys'][0] pkh = bh2u(bitcoin.hash_160(bfh(pubkey))) return '76a9' + push_script(pkh) + '88ac' diff --git a/lib/wallet.py b/lib/wallet.py index 04f95cc80..50cfe987f 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -1545,7 +1545,7 @@ class Simple_Wallet(Abstract_Wallet): def load_keystore(self): self.keystore = load_keystore(self.storage, 'keystore') self.is_segwit = self.keystore.is_segwit() - self.txin_type = 'p2wpkh-p2sh' if self.is_segwit else 'p2pkh' + self.txin_type = 'p2wpkh' if self.is_segwit else 'p2pkh' def get_pubkey(self, c, i): return self.derive_pubkeys(c, i) @@ -1635,15 +1635,6 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet): return addr -class P2SH: - - def pubkeys_to_redeem_script(self, pubkeys): - raise NotImplementedError() - - def pubkeys_to_address(self, pubkey): - redeem_script = self.pubkeys_to_redeem_script(pubkey) - return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script))) - class Standard_Wallet(Simple_Deterministic_Wallet): wallet_type = 'standard' @@ -1653,19 +1644,20 @@ class Standard_Wallet(Simple_Deterministic_Wallet): return transaction.segwit_script(pubkey) def pubkeys_to_address(self, pubkey): - if not self.is_segwit: + if self.txin_type == 'p2pkh': return bitcoin.public_key_to_p2pkh(bfh(pubkey)) - elif bitcoin.TESTNET: + elif self.txin_type == 'p2wpkh': + return bitcoin.hash_to_segwit_addr(hash_160(bfh(pubkey))) + elif self.txin_type == 'p2wpkh-p2sh': redeem_script = self.pubkeys_to_redeem_script(pubkey) return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script))) else: raise NotImplementedError() -class Multisig_Wallet(Deterministic_Wallet, P2SH): +class Multisig_Wallet(Deterministic_Wallet): # generic m of n gap_limit = 20 - txin_type = 'p2sh' def __init__(self, storage): self.wallet_type = storage.get('wallet_type') @@ -1675,9 +1667,19 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH): def get_pubkeys(self, c, i): return self.derive_pubkeys(c, i) - def redeem_script(self, c, i): - pubkeys = self.get_pubkeys(c, i) - return transaction.multisig_script(sorted(pubkeys), self.m) + def pubkeys_to_address(self, pubkey): + if self.txin_type == 'p2sh': + redeem_script = self.pubkeys_to_redeem_script(pubkey) + return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script))) + elif self.txin_type == 'p2wsh': + witness_script = self.pubkeys_to_redeem_script(pubkey) + return bitcoin.script_to_p2wsh(witness_script) + else: + raise NotImplementedError() + + #def redeem_script(self, c, i): + # pubkeys = self.get_pubkeys(c, i) + # return transaction.multisig_script(sorted(pubkeys), self.m) def pubkeys_to_redeem_script(self, pubkeys): return transaction.multisig_script(sorted(pubkeys), self.m) @@ -1691,6 +1693,8 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH): name = 'x%d/'%(i+1) self.keystores[name] = load_keystore(self.storage, name) self.keystore = self.keystores['x1/'] + self.is_segwit = self.keystore.is_segwit() + self.txin_type = 'p2wsh' if self.is_segwit else 'p2sh' def save_keystore(self): for name, k in self.keystores.items():