electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit e8b564c0e728e1ca15d39f48a0153c984e2acc3c
parent 4864c802dd0b82c294d4fd1858047990eb463d1e
Author: ThomasV <thomasv@electrum.org>
Date:   Mon, 25 Sep 2017 21:35:14 +0200

Extend Wallet Import Format with txin type. Extend class Imported_Wallet.

Diffstat:
Melectrum | 12++++++++++--
Mgui/qt/main_window.py | 12++++++++----
Mlib/__init__.py | 2+-
Mlib/base_wizard.py | 42++++++++++++++++++++++++------------------
Mlib/bitcoin.py | 86++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mlib/commands.py | 5+++--
Mlib/keystore.py | 47+++++++++++------------------------------------
Mlib/transaction.py | 3++-
Mlib/util.py | 4++++
Mlib/wallet.py | 191+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
10 files changed, 243 insertions(+), 161 deletions(-)

diff --git a/electrum b/electrum @@ -137,11 +137,19 @@ def run_non_RPC(config): wallet = Imported_Wallet(storage) for x in text.split(): wallet.import_address(x) + elif keystore.is_private_key_list(text): + k = keystore.Imported_KeyStore({}) + storage.put('keystore', k.dump()) + storage.put('use_encryption', bool(password)) + wallet = Imported_Wallet(storage) + for x in text.split(): + wallet.import_private_key(x, password) + storage.write() else: if keystore.is_seed(text): k = keystore.from_seed(text, passphrase) - elif keystore.is_any_key(text): - k = keystore.from_keys(text) + elif keystore.is_master_key(text): + k = keystore.from_master_key(text) else: sys.exit("Error: Seed or key not recognized") if password: diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -1864,20 +1864,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if not address: return try: - pk_list = self.wallet.get_private_key(address, password) + pk, redeem_script = self.wallet.export_private_key(address, password) except Exception as e: traceback.print_exc(file=sys.stdout) self.show_message(str(e)) return - d = WindowModalDialog(self, _("Private key")) d.setMinimumSize(600, 200) vbox = QVBoxLayout() vbox.addWidget( QLabel(_("Address") + ': ' + address)) vbox.addWidget( QLabel(_("Private key") + ':')) - keys_e = ShowQRTextEdit(text='\n'.join(pk_list)) + keys_e = ShowQRTextEdit(text=pk) keys_e.addCopyButton(self.app) vbox.addWidget(keys_e) + if redeem_script: + vbox.addWidget( QLabel(_("Redeem Script") + ':')) + rds_e = ShowQRTextEdit(text=redeem_script) + rds_e.addCopyButton(self.app) + vbox.addWidget(rds_e) vbox.addLayout(Buttons(CloseButton(d))) d.setLayout(vbox) d.exec_() @@ -2353,7 +2357,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if not self.wallet.can_import_privkey(): return title, msg = _('Import private keys'), _("Enter private keys") - self._do_import(title, msg, lambda x: self.wallet.import_key(x, password)) + self._do_import(title, msg, lambda x: self.wallet.import_private_key(x, password)) def update_fiat(self): b = self.fx and self.fx.is_enabled() diff --git a/lib/__init__.py b/lib/__init__.py @@ -1,6 +1,6 @@ from .version import ELECTRUM_VERSION from .util import format_satoshis, print_msg, print_error, set_verbosity -from .wallet import Synchronizer, Wallet, Imported_Wallet +from .wallet import Synchronizer, Wallet from .storage import WalletStorage from .coinchooser import COIN_CHOOSERS from .network import Network, pick_random_server diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -82,7 +82,7 @@ class BaseWizard(object): ('standard', _("Standard wallet")), ('2fa', _("Wallet with two-factor authentication")), ('multisig', _("Multi-signature wallet")), - ('imported', _("Watch Bitcoin addresses")), + ('imported', _("Import Bitcoin addresses or private keys")), ] choices = [pair for pair in wallet_kinds if pair[0] in wallet_types] self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type) @@ -102,7 +102,7 @@ class BaseWizard(object): self.load_2fa() action = self.storage.get_action() elif choice == 'imported': - action = 'import_addresses' + action = 'import_addresses_or_keys' self.run(action) def choose_multisig(self): @@ -137,26 +137,32 @@ class BaseWizard(object): self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) - def import_addresses(self): - v = keystore.is_address_list + def import_addresses_or_keys(self): + v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x) title = _("Import Bitcoin Addresses") - message = _("Enter a list of Bitcoin addresses. This will create a watching-only wallet.") - self.add_xpub_dialog(title=title, message=message, run_next=self.on_import_addresses, is_valid=v) - - def on_import_addresses(self, text): - assert keystore.is_address_list(text) - self.wallet = Imported_Wallet(self.storage) - for x in text.split(): - self.wallet.import_address(x) + message = _("Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.") + self.add_xpub_dialog(title=title, message=message, run_next=self.on_import, is_valid=v) + + def on_import(self, text): + if keystore.is_address_list(text): + self.wallet = Imported_Wallet(self.storage) + for x in text.split(): + self.wallet.import_address(x) + elif keystore.is_private_key_list(text): + k = keystore.Imported_KeyStore({}) + self.storage.put('keystore', k.dump()) + self.wallet = Imported_Wallet(self.storage) + for x in text.split(): + self.wallet.import_private_key(x, None) self.terminate() def restore_from_key(self): if self.wallet_type == 'standard': - v = keystore.is_any_key - title = _("Create keystore from keys") + v = keystore.is_master_key + title = _("Create keystore from a master key") message = ' '.join([ - _("To create a watching-only wallet, please enter your master public key (xpub)."), - _("To create a spending wallet, please enter a master private key (xprv), or a list of Bitcoin private keys.") + _("To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub)."), + _("To create a spending wallet, please enter a master private key (xprv/yprv/zprv).") ]) self.add_xpub_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v) else: @@ -164,7 +170,7 @@ class BaseWizard(object): self.add_cosigner_dialog(index=i, run_next=self.on_restore_from_key, is_valid=keystore.is_bip32_key) def on_restore_from_key(self, text): - k = keystore.from_keys(text) + k = keystore.from_master_key(text) self.on_keystore(k) def choose_hw_device(self): @@ -357,7 +363,7 @@ class BaseWizard(object): self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) def on_cosigner(self, text, password, i): - k = keystore.from_keys(text, password) + k = keystore.from_master_key(text, password) self.on_keystore(k) def choose_seed_type(self): diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -35,7 +35,7 @@ import pyaes from .util import bfh, bh2u, to_string from . import version -from .util import print_error, InvalidPassword, assert_bytes, to_bytes +from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict from . import segwit_addr def read_json_dict(filename): @@ -65,7 +65,6 @@ XPUB_HEADERS = { # Bitcoin network constants TESTNET = False -NOLNET = False ADDRTYPE_P2PKH = 0 ADDRTYPE_P2SH = 5 SEGWIT_HRP = "bc" @@ -334,6 +333,30 @@ def script_to_p2wsh(script): return hash_to_segwit_addr(sha256(bfh(script))) +def pubkey_to_address(txin_type, pubkey): + if txin_type == 'p2pkh': + return public_key_to_p2pkh(bfh(pubkey)) + elif txin_type == 'p2wpkh': + return hash_to_segwit_addr(hash_160(bfh(pubkey))) + elif txin_type == 'p2wpkh-p2sh': + scriptSig = transaction.p2wpkh_nested_script(pubkey) + return hash160_to_p2sh(hash_160(bfh(scriptSig))) + else: + raise NotImplementedError(txin_type) + +def redeem_script_to_address(txin_type, redeem_script): + if txin_type == 'p2sh': + return hash160_to_p2sh(hash_160(bfh(redeem_script))) + elif txin_type == 'p2wsh': + return script_to_p2wsh(redeem_script) + elif txin_type == 'p2wsh-p2sh': + scriptSig = transaction.p2wsh_nested_script(redeem_script) + return hash160_to_p2sh(hash_160(bfh(scriptSig))) + else: + raise NotImplementedError(txin_type) + + + def address_to_script(addr): witver, witprog = segwit_addr.decode(SEGWIT_HRP, addr) if witprog is not None: @@ -448,33 +471,42 @@ def DecodeBase58Check(psz): return key -def PrivKeyToSecret(privkey): - return privkey[9:9+32] +# extended key export format for segwit -def SecretToASecret(secret, compressed=False): - addrtype = ADDRTYPE_P2PKH - vchIn = bytes([(addrtype+128)&255]) + secret - if compressed: vchIn += b'\01' +SCRIPT_TYPES = { + 'p2pkh':0, + 'p2wpkh':1, + 'p2wpkh-p2sh':2, + 'p2sh':5, + 'p2wsh':6, + 'p2wsh-p2sh':7 +} + + +def serialize_privkey(secret, compressed, txin_type): + prefix = bytes([(SCRIPT_TYPES[txin_type]+128)&255]) + suffix = b'\01' if compressed else b'' + vchIn = prefix + secret + suffix return EncodeBase58Check(vchIn) -def ASecretToSecret(key): - addrtype = ADDRTYPE_P2PKH +def deserialize_privkey(key): + # whether the pubkey is compressed should be visible from the keystore vch = DecodeBase58Check(key) - if vch and vch[0] == ((addrtype+128)&255): - return vch[1:] - elif is_minikey(key): - return minikey_to_private_key(key) + if is_minikey(key): + return 'p2pkh', minikey_to_private_key(key), True + elif vch: + txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - 128] + assert len(vch) in [33, 34] + compressed = len(vch) == 34 + return txin_type, vch[1:33], compressed else: return False -def regenerate_key(sec): - b = ASecretToSecret(sec) - if not b: - return False - b = b[0:32] - return EC_KEY(b) +def regenerate_key(pk): + assert len(pk) == 32 + return EC_KEY(pk) def GetPubKey(pubkey, compressed=False): @@ -486,15 +518,12 @@ def GetSecret(pkey): def is_compressed(sec): - b = ASecretToSecret(sec) - return len(b) == 33 + return deserialize_privkey(sec)[2] -def public_key_from_private_key(sec): +def public_key_from_private_key(pk, compressed): # rebuild public key from private key, compressed or uncompressed - pkey = regenerate_key(sec) - assert pkey - compressed = is_compressed(sec) + pkey = regenerate_key(pk) public_key = GetPubKey(pkey.pubkey, compressed) return bh2u(public_key) @@ -533,7 +562,7 @@ def is_p2sh(addr): def is_private_key(key): try: - k = ASecretToSecret(key) + k = deserialize_privkey(key) return k is not False except: return False @@ -970,5 +999,4 @@ def bip32_public_derivation(xpub, branch, sequence): def bip32_private_key(sequence, k, chain): for i in sequence: k, chain = CKD_priv(k, chain, i) - return SecretToASecret(k, True) - + return k diff --git a/lib/commands.py b/lib/commands.py @@ -364,11 +364,11 @@ class Commands: @command('wp') def importprivkey(self, privkey, password=None): - """Import a private key. """ + """Import a private key.""" if not self.wallet.can_import_privkey(): return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key." try: - addr = self.wallet.import_key(privkey, password) + addr = self.wallet.import_private_key(privkey, password) out = "Keypair imported: " + addr except BaseException as e: out = "Error: " + str(e) @@ -687,6 +687,7 @@ param_descriptions = { 'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.', 'requested_amount': 'Requested amount (in BTC).', 'outputs': 'list of ["address", amount]', + 'redeem_script': 'redeem script (hexadecimal)', } command_options = { diff --git a/lib/keystore.py b/lib/keystore.py @@ -141,24 +141,22 @@ class Imported_KeyStore(Software_KeyStore): pubkey = list(self.keypairs.keys())[0] self.get_private_key(pubkey, password) - def import_key(self, sec, password): - try: - pubkey = public_key_from_private_key(sec) - except Exception: - raise BaseException('Invalid private key') - # allow overwrite + def import_privkey(self, sec, password): + txin_type, privkey, compressed = deserialize_privkey(sec) + pubkey = public_key_from_private_key(privkey, compressed) self.keypairs[pubkey] = pw_encode(sec, password) - return pubkey + return txin_type, pubkey def delete_imported_key(self, key): self.keypairs.pop(key) def get_private_key(self, pubkey, password): - pk = pw_decode(self.keypairs[pubkey], password) + sec = pw_decode(self.keypairs[pubkey], password) + txin_type, privkey, compressed = deserialize_privkey(sec) # this checks the password - if pubkey != public_key_from_private_key(pk): + if pubkey != public_key_from_private_key(privkey, compressed): raise InvalidPassword() - return pk + return privkey def get_pubkey_derivation(self, x_pubkey): if x_pubkey[0:2] in ['02', '03', '04']: @@ -180,8 +178,6 @@ class Imported_KeyStore(Software_KeyStore): c = pw_encode(b, new_password) self.keypairs[k] = c - def txin_type(self): - return 'p2pkh' class Deterministic_KeyStore(Software_KeyStore): @@ -277,17 +273,6 @@ class Xpub: return return derivation - def txin_type(self): - xtype = deserialize_xpub(self.xpub)[0] - if xtype == 'standard': - return 'p2pkh' - elif xtype == 'segwit': - return 'p2wpkh' - elif xtype == 'segwit_p2sh': - return 'p2wpkh-p2sh' - else: - raise BaseException('unknown txin_type', xtype) - class BIP32_KeyStore(Deterministic_KeyStore, Xpub): @@ -411,11 +396,6 @@ class Old_KeyStore(Deterministic_KeyStore): def get_sequence(self, mpk, for_change, n): return string_to_number(Hash(("%d:%d:"%(n, for_change)).encode('ascii') + bfh(mpk))) - def get_address(self, for_change, n): - pubkey = self.get_pubkey(for_change, n) - address = public_key_to_p2pkh(bfh(pubkey)) - return address - @classmethod def get_pubkey_from_mpk(self, mpk, for_change, n): z = self.get_sequence(mpk, for_change, n) @@ -431,8 +411,7 @@ class Old_KeyStore(Deterministic_KeyStore): order = generator_secp256k1.order() secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % order pk = number_to_string(secexp, generator_secp256k1.order()) - compressed = False - return SecretToASecret(pk, compressed) + return pk def get_private_key(self, sequence, password): seed = self.get_hex_seed(password) @@ -491,8 +470,6 @@ class Old_KeyStore(Deterministic_KeyStore): decoded = pw_decode(self.seed, old_password) self.seed = pw_encode(decoded, new_password) - def txin_type(self): - return 'p2phk' class Hardware_KeyStore(KeyStore, Xpub): @@ -692,7 +669,7 @@ def is_private_key_list(text): is_mpk = lambda x: is_old_mpk(x) or is_xpub(x) is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x) -is_any_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x) or is_private_key_list(x) +is_master_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x) is_private_key = lambda x: is_xprv(x) or is_private_key_list(x) is_bip32_key = lambda x: is_xprv(x) or is_xpub(x) @@ -740,15 +717,13 @@ def from_xprv(xprv): k.xpub = xpub return k -def from_keys(text): +def from_master_key(text): if is_xprv(text): k = from_xprv(text) elif is_old_mpk(text): k = from_old_mpk(text) elif is_xpub(text): k = from_xpub(text) - elif is_private_key_list(text): - k = from_private_key_list(text) else: raise BaseException('Invalid key') return k diff --git a/lib/transaction.py b/lib/transaction.py @@ -890,7 +890,8 @@ class Transaction: if x_pubkey in keypairs.keys(): print_error("adding signature for", x_pubkey) sec = keypairs.get(x_pubkey) - pubkey = public_key_from_private_key(sec) + compressed = True + pubkey = public_key_from_private_key(sec, compressed) # add signature pre_hash = Hash(bfh(self.serialize_preimage(i))) pkey = regenerate_key(sec) diff --git a/lib/util.py b/lib/util.py @@ -40,6 +40,10 @@ from .i18n import _ import urllib.request, urllib.parse, urllib.error import queue +def inv_dict(d): + return {v: k for k, v in d.items()} + + base_units = {'BTC':8, 'mBTC':5, 'uBTC':2} fee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')] diff --git a/lib/wallet.py b/lib/wallet.py @@ -262,22 +262,24 @@ class Abstract_Wallet(PrintError): return address in self.change_addresses def get_address_index(self, address): - if self.keystore.can_import(): - for pubkey in self.keystore.keypairs.keys(): - if self.pubkeys_to_address(pubkey) == address: - return pubkey - elif address in self.receiving_addresses: + if address in self.receiving_addresses: return False, self.receiving_addresses.index(address) if address in self.change_addresses: return True, self.change_addresses.index(address) raise Exception("Address not found", address) - def get_private_key(self, address, password): + def export_private_key(self, address, password): + """ extended WIF format """ if self.is_watching_only(): return [] index = self.get_address_index(address) pk = self.keystore.get_private_key(index, password) - return [pk] + if self.txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: + pubkeys = self.get_public_keys(address) + redeem_script = self.pubkeys_to_redeem_script(pubkeys) + else: + redeem_script = None + return bitcoin.serialize_privkey(pk, True, self.txin_type), redeem_script def get_public_key(self, address): if self.keystore.can_import(): @@ -1343,28 +1345,41 @@ class Imported_Wallet(Abstract_Wallet): def __init__(self, storage): Abstract_Wallet.__init__(self, storage) + def is_watching_only(self): + return self.keystore is None + + def get_keystores(self): + return [self.keystore] if self.keystore else [] + + def check_password(self, password): + self.keystore.check_password(password) + + def can_import_privkey(self): + return bool(self.keystore) + def load_keystore(self): - pass + self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None - def load_addresses(self): - self.addresses = self.storage.get('addresses', []) - self.receiving_addresses = self.addresses - self.change_addresses = [] + def save_keystore(self): + self.storage.put('keystore', self.keystore.dump()) - def get_keystores(self): - return [] + def load_addresses(self): + self.addresses = self.storage.get('addresses', {}) + # convert list + if type(self.addresses) is list: + self.addresses = dict([(x, None) for x in self.addresses]) - def has_password(self): - return False + def save_addresses(self): + self.storage.put('addresses', self.addresses) def can_change_password(self): - return False + return not self.is_watching_only() def can_import_address(self): - return True + return self.is_watching_only() - def is_watching_only(self): - return True + def can_delete_address(self): + return self.is_watching_only() def has_seed(self): return False @@ -1375,6 +1390,9 @@ class Imported_Wallet(Abstract_Wallet): def is_used(self, address): return False + def is_change(self, address): + return False + def get_master_public_keys(self): return [] @@ -1385,38 +1403,84 @@ class Imported_Wallet(Abstract_Wallet): return '' def get_addresses(self, include_change=False): - return self.addresses + return sorted(self.addresses.keys()) + + def get_receiving_addresses(self): + return self.get_addresses() + + def get_change_addresses(self): + return [] def import_address(self, address): if address in self.addresses: - return - self.addresses.append(address) + return '' + self.addresses[address] = {} self.storage.put('addresses', self.addresses) self.storage.write() self.add_address(address) return address - def can_delete_address(self): - return True - def delete_address(self, address): if address not in self.addresses: return - self.addresses.remove(address) + self.addresses.pop(address) self.storage.put('addresses', self.addresses) self.storage.write() - def get_receiving_addresses(self): - return self.addresses[:] + def get_address_index(self, address): + if self.keystore.can_import(): + return self.addresses[address]['pubkey'] + + def import_private_key(self, sec, pw, redeem_script=None): + try: + txin_type, pubkey = self.keystore.import_privkey(sec, pw) + except Exception: + raise BaseException('Invalid private key', sec) + if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']: + if redeem_script is not None: + raise BaseException('Cannot use redeem script with', txin_type, sec) + addr = bitcoin.pubkey_to_address(txin_type, pubkey) + elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']: + if redeem_script is None: + raise BaseException('Redeem script required for', txin_type, sec) + addr = bitcoin.redeem_script_to_address(txin_type, redeem_script) + else: + raise NotImplementedError(self.txin_type) + self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':redeem_script} + self.save_keystore() + self.save_addresses() + self.storage.write() + self.add_address(addr) + return addr - def get_change_addresses(self): - return [] + def export_private_key(self, address, password): + txin_type, pubkey, redeem_script = self.addresses[address] + sec = pw_decode(self.keystore.keypairs[pubkey], password) + return sec, redeem_script def add_input_sig_info(self, txin, address): - addrtype, hash160 = b58_address_to_hash160(address) - x_pubkey = 'fd' + bh2u(bytes([addrtype]) + hash160) - txin['x_pubkeys'] = [x_pubkey] - txin['signatures'] = [None] + if self.is_watching_only(): + addrtype, hash160 = b58_address_to_hash160(address) + x_pubkey = 'fd' + bh2u(bytes([addrtype]) + hash160) + txin['x_pubkeys'] = [x_pubkey] + txin['signatures'] = [None] + return + + txin_type = self.addresses[address]['txin_type'] + txin['type'] = txin_type + if txin_type in ['p2pkh', 'p2wkh', 'p2wkh-p2sh']: + pubkey = self.addresses[address]['pubkey'] + txin['num_sig'] = 1 + txin['x_pubkeys'] = [pubkey] + txin['signatures'] = [None] + else: + redeem_script = self.addresses[address]['redeem_script'] + num_sig = 2 + num_keys = 3 + txin['num_sig'] = num_sig + txin['redeem_script'] = redeem_script + txin['signatures'] = [None] * num_keys + class Deterministic_Wallet(Abstract_Wallet): @@ -1544,7 +1608,18 @@ class Simple_Wallet(Abstract_Wallet): def load_keystore(self): self.keystore = load_keystore(self.storage, 'keystore') - self.txin_type = self.keystore.txin_type() + try: + xtype = deserialize_xpub(self.keystore.xpub)[0] + except: + xtype = 'standard' + if xtype == 'standard': + self.txin_type = 'p2pkh' + elif xtype == 'segwit': + self.txin_type = 'p2wpkh' + elif xtype == 'segwit_p2sh': + self.txin_type = 'p2wpkh-p2sh' + else: + raise BaseException('unknown txin_type', xtype) def get_pubkey(self, c, i): return self.derive_pubkeys(c, i) @@ -1623,15 +1698,6 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet): def can_import_privkey(self): return self.keystore.can_import() - def import_key(self, pk, pw): - pubkey = self.keystore.import_key(pk, pw) - self.save_keystore() - addr = self.pubkeys_to_address(pubkey) - self.receiving_addresses.append(addr) - self.save_addresses() - self.storage.write() - self.add_address(addr) - return addr @@ -1639,16 +1705,7 @@ class Standard_Wallet(Simple_Deterministic_Wallet): wallet_type = 'standard' def pubkeys_to_address(self, pubkey): - if self.txin_type == 'p2pkh': - return bitcoin.public_key_to_p2pkh(bfh(pubkey)) - elif self.txin_type == 'p2wpkh': - return bitcoin.hash_to_segwit_addr(hash_160(bfh(pubkey))) - elif self.txin_type == 'p2wpkh-p2sh': - scriptSig = transaction.p2wpkh_nested_script(pubkey) - return bitcoin.hash160_to_p2sh(hash_160(bfh(scriptSig))) - else: - raise NotImplementedError(self.txin_type) - + return bitcoin.pubkey_to_address(self.txin_type, pubkey) class Multisig_Wallet(Deterministic_Wallet): # generic m of n @@ -1663,18 +1720,8 @@ class Multisig_Wallet(Deterministic_Wallet): return self.derive_pubkeys(c, i) def pubkeys_to_address(self, pubkeys): - if self.txin_type == 'p2sh': - redeem_script = self.pubkeys_to_redeem_script(pubkeys) - return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script))) - elif self.txin_type == 'p2wsh': - witness_script = self.pubkeys_to_redeem_script(pubkeys) - return bitcoin.script_to_p2wsh(witness_script) - elif self.txin_type == 'p2wsh-p2sh': - witness_script = self.pubkeys_to_redeem_script(pubkeys) - scriptSig = transaction.p2wsh_nested_script(witness_script) - return bitcoin.hash160_to_p2sh(hash_160(bfh(scriptSig))) - else: - raise NotImplementedError(self.txin_type) + redeem_script = self.pubkeys_to_redeem_script(pubkeys) + return bitcoin.redeem_script_to_address(self.txin_type, redeem_script) def pubkeys_to_redeem_script(self, pubkeys): return transaction.multisig_script(sorted(pubkeys), self.m) @@ -1688,7 +1735,15 @@ class Multisig_Wallet(Deterministic_Wallet): name = 'x%d/'%(i+1) self.keystores[name] = load_keystore(self.storage, name) self.keystore = self.keystores['x1/'] - self.txin_type = self.keystore.txin_type() + xtype = deserialize_xpub(self.keystore.xpub)[0] + if xtype == 'standard': + self.txin_type = 'p2sh' + elif xtype == 'segwit': + self.txin_type = 'p2wsh' + elif xtype == 'segwit_p2sh': + self.txin_type = 'p2wsh-p2sh' + else: + raise BaseException('unknown txin_type', xtype) def save_keystore(self): for name, k in self.keystores.items():