def run_non_RPC(config): """Most commands should go through the daemon or RPC, especially commands that operate on wallets.""" cmdname = config.get('cmd') def get_wallet_path() -> str: wallet_path = config.get_cmdline_wallet_filepath() if wallet_path is None: sys.exit("error: no wallet path provided") final_path = WalletStorage.canonical_path(wallet_path) if WalletStorage.files_are_matched_by_path(wallet_path): sys.exit(f"error: wallet already exists: {final_path}") return final_path if cmdname in {'create_wallet', 'create_account'}: if not config.cmdline_options.get('nopasswordcheck'): password = prompt_password("Password:"******"error: wallet creation requires a password") if cmdname == 'create_wallet': wallet_path = get_wallet_path() storage = WalletStorage.create(wallet_path, password) storage.close() print(f"Wallet saved in '{wallet_path}'") sys.exit(0) elif cmdname == 'create_account': wallet_path = config.get_cmdline_wallet_filepath() storage = WalletStorage.create(wallet_path, password) parent_wallet = Wallet(storage) # create an account for the Wallet (only random new seeds supported - no importing) text_type = KeystoreTextType.EXTENDED_PRIVATE_KEY text_match = os.getenv("ELECTRUMSV_ACCOUNT_XPRV") if not text_match: # generate a random account seed data = urandom(64) coin = bitcoinx.BitcoinRegtest xprv = bitcoinx.BIP32PrivateKey._from_parts( data[:32], data[32:], coin) text_match = xprv.to_extended_key_string() keystore = instantiate_keystore_from_text(text_type, text_match, password, derivation_text=None, passphrase=None, watch_only=False) parent_wallet.create_account_from_keystore(keystore) print(f"New standard (bip32) account created for: '{wallet_path}'") sys.exit(0) else: sys.exit("error: unrecognised command")
def check_legacy_parent_of_multisig_wallet(wallet: Wallet) -> None: assert len(wallet.get_accounts()) == 1 account: MultisigAccount = wallet.get_accounts()[0] m = account.m n = account.n parent_keystores = wallet.get_keystores() assert len(parent_keystores) == 1 keystore = parent_keystores[0] child_keystores = keystore.get_cosigner_keystores() assert len(child_keystores) == n parent_data = keystore.to_derivation_data() for i in range(n): masterkey_row = child_keystores[i].to_masterkey_row() assert masterkey_row.derivation_type == DerivationType.BIP32 keystore_data = parent_data["cosigner-keys"][i][1] if len(keystore_data) == 3: assert keystore_data['seed'] is not None # == seed_words assert keystore_data['xpub'] is not None assert keystore_data['xprv'] is not None else: assert len(keystore_data) == 2 assert keystore_data['xpub'] is not None assert keystore_data['xprv'] is None
def test_multisig(self, tmp_storage) -> None: wallet = Wallet(tmp_storage) seed_words = ( 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon ' + 'rookie sure') ks1 = from_seed(seed_words, '') ks2 = from_xpub( 'xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDben' + 'T33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec') keystore = Multisig_KeyStore({'m': 2, 'n': 2, "cosigner-keys": []}) keystore.add_cosigner_keystore(ks1) keystore.add_cosigner_keystore(ks2) assert not keystore.is_watching_only() assert 2 == len(keystore.get_cosigner_keystores()) masterkey_row = wallet.create_masterkey_from_keystore(keystore) account_row = AccountRow(1, masterkey_row.masterkey_id, ScriptType.MULTISIG_BARE, 'text') account = MultisigAccount(wallet, account_row, [], []) wallet.register_account(account.get_id(), account) check_legacy_parent_of_multisig_wallet(wallet) check_create_keys(wallet, account_row.default_script_type)
def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists(): print("Wallet not found. try 'electrum-sv create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'') self.encoding = locale.getpreferredencoding() self.stdscr = curses.initscr() curses.noecho() curses.cbreak() curses.start_color() curses.use_default_colors() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN) curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) self.stdscr.keypad(1) self.stdscr.border(0) self.maxy, self.maxx = self.stdscr.getmaxyx() self.set_cursor(0) self.w = curses.newwin(10, 50, 5, 5) disable_verbose_logging() self.tab = 0 self.pos = 0 self.popup_pos = 0 self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.history = None if self.network: self.network.register_callback(self.update, ['updated']) self.tab_names = [ _("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner") ] self.num_tabs = len(self.tab_names)
def test_imported_privkey(self, tmp_storage) -> None: wallet = Wallet(tmp_storage) account = wallet.create_account_from_text_entries(KeystoreTextType.PRIVATE_KEYS, ScriptType.P2PKH, [ "KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6" ], "password") keypairs = {'02c6467b7e621144105ed3e4835b0b4ab7e35266a2ae1c4f8baa19e9ca93452997': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6'} check_legacy_parent_of_imported_privkey_wallet(wallet, keypairs=keypairs, password='******')
def test_imported_pubkey(self, tmp_storage) -> None: text = """ 15hETetDmcXm1mM4sEf7U2KXC9hDHFMSzz 1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6 """ wallet = Wallet(tmp_storage) account = wallet.create_account_from_text_entries(KeystoreTextType.ADDRESSES, ScriptType.NONE, [ "15hETetDmcXm1mM4sEf7U2KXC9hDHFMSzz", "1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6" ], "password") check_legacy_parent_of_imported_address_wallet(wallet)
def run_and_get_wallet(self): path = self.storage.path if self.storage.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are not supported.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = (_('Your accounts have been moved to') + ':\n' + file_list + '\n\n' + _('Do you want to delete the old file') + ':\n' + path) if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return if self.storage.requires_upgrade(): self.hide() msg = _( "The format of your wallet '%s' must be upgraded for ElectrumSV. " "This change will not be backward compatible" % path) if not self.question(msg): return self.storage.upgrade() self.wallet = Wallet(self.storage) return self.wallet action = self.storage.get_action() if action and action != 'new': self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run self.run(action) return self.wallet self.wallet = Wallet(self.storage) return self.wallet
def run_and_get_wallet(self): path = self.storage.path if self.storage.requires_split(): msg = _("The wallet '{}' contains multiple accounts, which are not supported.\n\n" "Do you want to split your wallet into multiple files?").format(path) if not MessageBox.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = (_('Your accounts have been moved to') + ':\n' + file_list + '\n\n' + _('Do you want to delete the old file') + ':\n' + path) if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return if self.storage.requires_upgrade(): msg = _("The format of your wallet '%s' must be upgraded for ElectrumSV. " "This change will not be backward compatible, "+ "and your existing wallet will be backed up. Proceed?") % path if not MessageBox.question(msg): return self.storage.upgrade() self.wallet = Wallet(self.storage) return self.wallet action = self.storage.get_action() if action and action != 'new': msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not MessageBox.question(msg): if MessageBox.question(_("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run, unless they go back. self.run(action) if action == "new" and self.wallet: # We forceably save new wallets in order to get the initial state synced on disk # that the user can both find it if ESV crashes, and that the externally referenced # and encrypted data has synchronised persisted keys self.wallet.save_storage() return self.wallet self.wallet = Wallet(self.storage) return self.wallet
def test_imported_pubkey(self, tmp_storage) -> None: text = """ 15hETetDmcXm1mM4sEf7U2KXC9hDHFMSzz 1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6 """ wallet = Wallet(tmp_storage) account = ImportedAddressAccount.from_text(wallet, None, text) check_legacy_parent_of_imported_address_wallet(wallet)
def check_legacy_parent_of_imported_privkey_wallet(wallet: Wallet, keypairs: Optional[Dict[str, str]]=None, password: Optional[str]=None) -> None: assert len(wallet.get_accounts()) == 1 account: ImportedPrivkeyAccount = wallet.get_accounts()[0] parent_keystores = wallet.get_keystores() assert len(parent_keystores) == 0 child_keystores = account.get_keystores() assert len(child_keystores) == 1 assert child_keystores[0] is not None assert not child_keystores[0].has_masterkey() with pytest.raises(IncompatibleWalletError): child_keystores[0].to_masterkey_row() with pytest.raises(IncompatibleWalletError): child_keystores[0].to_derivation_data() keyinstance_datas = child_keystores[0].get_keyinstance_derivation_data() assert len(keyinstance_datas) == 1 if keypairs is not None: for key_id, data in keyinstance_datas: assert pw_decode(data['prv'], password) == keypairs[data['pub']]
def check_legacy_parent_of_standard_wallet( wallet: Wallet, seed_words: Optional[str] = None, is_bip39: bool = False, password: Optional[str] = None) -> None: assert len(wallet.get_accounts()) == 1 account: StandardAccount = wallet.get_accounts()[0] parent_keystores = wallet.get_keystores() assert len(parent_keystores) == 1 child_keystores = account.get_keystores() assert len(child_keystores) == 1 assert parent_keystores[0] is child_keystores[0] assert password is not None assert not child_keystores[0].has_seed() or child_keystores[0].get_seed( password) assert type(child_keystores[0].get_passphrase(password)) is str assert child_keystores[0].get_master_private_key(password) keystore_data = parent_keystores[0].to_derivation_data() entry_count = 4 if is_bip39: entry_count = 3 assert len(keystore_data) == entry_count, keystore_data assert 'xpub' in keystore_data assert 'xprv' in keystore_data keystore_encrypted = False try: parent_keystores[0].check_password(None) except InvalidPassword: keystore_encrypted = True assert "encrypted" not in wallet.name() or keystore_encrypted if is_bip39: assert "seed" not in keystore_data else: if seed_words is None: assert "seed" in keystore_data else: assert keystore_data['seed'] == seed_words
def test_old(self, tmp_storage) -> None: seed_words = ('powerful random nobody notice nothing important '+ 'anyway look away hidden message over') child_keystore = from_seed(seed_words, '') assert isinstance(child_keystore, Old_KeyStore) wallet = Wallet(tmp_storage) masterkey_row = wallet.create_masterkey_from_keystore(child_keystore) account_row = AccountRow(1, masterkey_row.masterkey_id, ScriptType.P2PKH, '...') account = StandardAccount(wallet, account_row, [], []) wallet.register_account(account.get_id(), account) parent_keystores = wallet.get_keystores() assert len(parent_keystores) == 1 child_keystores = account.get_keystores() assert len(child_keystores) == 1 assert parent_keystores[0] is child_keystores[0] masterkey_row = parent_keystores[0].to_masterkey_row() assert masterkey_row.derivation_type == DerivationType.ELECTRUM_OLD keystore_data = parent_keystores[0].to_derivation_data() assert len(keystore_data) == 3 assert 'mpk' in keystore_data assert 'seed' in keystore_data assert 'subpaths' in keystore_data check_create_keys(wallet, account_row.default_script_type)
def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print("Wallet not found. try 'electrum-sv create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'updated', 'banner']) self.commands = [ _("[h] - displays this help text"), _("[i] - display transaction history"), _("[o] - enter payment order"), _("[p] - print stored payment order"), _("[s] - send stored payment order"), _("[r] - show own receipt addresses"), _("[c] - display contacts"), _("[b] - print server banner"), _("[q] - quit"), ] self.num_commands = len(self.commands)
def run_offline_command(config, config_options): cmdname = config.get('cmd') cmd = known_commands[cmdname] password = config_options.get('password') if cmd.requires_wallet: storage = WalletStorage(config.get_wallet_path()) if storage.is_encrypted(): storage.decrypt(password) wallet = Wallet(storage) else: wallet = None # check password if cmd.requires_password and storage.get('use_encryption'): try: seed = wallet.check_password(password) except InvalidPassword: print("Error: This password does not decode this wallet.") sys.exit(1) if cmd.requires_network: print("Warning: running command offline") # arguments passed to function args = [config.get(x) for x in cmd.params] # decode json arguments if cmdname not in ('setconfig', ): args = [json_decode(arg) for arg in args] # options kwargs = {} for x in cmd.options: kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x)) cmd_runner = Commands(config, wallet, None) func = getattr(cmd_runner, cmd.name) result = func(*args, **kwargs) # save wallet if wallet: wallet.storage.write() return result
def wallet_widgets(self, wallet: Wallet): usechange_cb = QCheckBox(_('Use change addresses')) usechange_cb.setChecked(wallet.get_use_change()) usechange_cb.setEnabled(app_state.config.is_modifiable('use_change')) usechange_cb.setToolTip( _('Using a different change key each time improves your privacy by ' 'making it more difficult for others to analyze your transactions.' )) def on_usechange(state): usechange_result = state == Qt.Checked if wallet.get_use_change() != usechange_result: wallet.set_use_change(usechange_result) multiple_cb.setEnabled(wallet.get_use_change()) usechange_cb.stateChanged.connect(on_usechange) multiple_cb = QCheckBox(_('Use multiple change addresses')) multiple_cb.setChecked(wallet.get_multiple_change()) multiple_cb.setEnabled(wallet.get_use_change()) multiple_cb.setToolTip('\n'.join([ _('In some cases, use up to 3 change keys in order to break ' 'up large coin amounts and obfuscate the recipient key.'), _('This may result in higher transactions fees.') ])) def on_multiple(state): multiple = state == Qt.Checked if wallet.get_multiple_change() != multiple: wallet.set_multiple_change(multiple) multiple_cb.stateChanged.connect(on_multiple) return [ (usechange_cb, ), (multiple_cb, ), ]
def check_legacy_parent_of_hardware_wallet(wallet: Wallet) -> None: assert len(wallet.get_accounts()) == 1 child_account = wallet.get_accounts()[0] parent_keystores = wallet.get_keystores() assert len(parent_keystores) == 1 child_keystores = child_account.get_keystores() assert len(child_keystores) == 1 assert parent_keystores[0] is child_keystores[0] masterkey_row = parent_keystores[0].to_masterkey_row() assert masterkey_row.derivation_type == DerivationType.HARDWARE keystore_data = parent_keystores[0].to_derivation_data() # General hardware wallet. entry_count = 5 if keystore_data['hw_type'] == "ledger": # Ledger wallets extend the keystore. assert "cfg" in keystore_data entry_count = 6 assert len(keystore_data) == entry_count assert 'hw_type' in keystore_data assert 'label' in keystore_data assert "derivation" in keystore_data assert "subpaths" in keystore_data
def test_imported_privkey(self, tmp_storage) -> None: text = """ KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6 """ wallet = Wallet(tmp_storage) script_type = ScriptType.P2PKH account = ImportedPrivkeyAccount.from_text(wallet, None, script_type, 'password', text) keypairs = { '02c6467b7e621144105ed3e4835b0b4ab7e35266a2ae1c4f8baa19e9ca93452997': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6' } check_legacy_parent_of_imported_privkey_wallet(wallet, keypairs=keypairs, password='******')
def test_standard_electrum(self, tmp_storage) -> None: password = '******' seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' child_keystore = from_seed(seed_words, '') wallet = Wallet(tmp_storage) masterkey_row = wallet.create_masterkey_from_keystore(child_keystore) wallet.update_password(password) account_row = AccountRow(1, masterkey_row.masterkey_id, ScriptType.P2PKH, '...') account = StandardAccount(wallet, account_row, [], []) wallet.register_account(account.get_id(), account) check_legacy_parent_of_standard_wallet(wallet, password=password) check_create_keys(wallet, account_row.default_script_type)
def run_offline_command(config, config_options): cmdname = config.get('cmd') cmd = known_commands[cmdname] password = config_options.get('password') if cmd.requires_wallet: wallet_path = config.get_wallet_path() if not WalletStorage.files_are_matched_by_path(wallet_path): print("Error: wallet does not exist at given path") sys.exit(1) storage = WalletStorage(wallet_path) wallet = Wallet(storage) else: wallet = None if cmd.requires_password: try: wallet.check_password(password) except InvalidPassword: print( "Error: This password cannot access the wallet's private data." ) sys.exit(1) if cmd.requires_network: print("Warning: running command offline") # arguments passed to function args = [config.get(x) for x in cmd.params] # decode json arguments if cmdname not in ('setconfig', ): args = [json_decode(arg) for arg in args] # options kwargs = {} for x in cmd.options: kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x)) cmd_runner = Commands(config, wallet, None) func = getattr(cmd_runner, cmd.name) result = func(*args, **kwargs) # save wallet if wallet: wallet.save_storage() return result
def test_legacy_wallet_loading(storage_info: WalletStorageInfo) -> None: # When a wallet is composed of multiple files, we need to know which to load. wallet_filenames = [] if storage_info.kind != StorageKind.DATABASE: wallet_filenames.append(storage_info.filename) if storage_info.kind in (StorageKind.DATABASE, StorageKind.HYBRID): wallet_filenames.append(storage_info.filename + DATABASE_EXT) temp_dir = tempfile.mkdtemp() for _wallet_filename in wallet_filenames: source_wallet_path = os.path.join(TEST_WALLET_PATH, _wallet_filename) wallet_path = os.path.join(temp_dir, _wallet_filename) shutil.copyfile(source_wallet_path, wallet_path) wallet_filename = storage_info.filename wallet_path = os.path.join(temp_dir, wallet_filename) if "testnet" in wallet_filename: Net.set_to(SVTestnet) if storage_info.kind == StorageKind.HYBRID: pytest.xfail("old development database wallets not supported yet") password = None storage = WalletStorage(wallet_path) if "passworded" in wallet_filename: password = "******" text_store = storage.get_text_store() text_store.load_data(text_store.decrypt(password)) if "encrypted" in wallet_filename: password = "******" check_no_password = False storage.upgrade(password is not None, password) try: wallet = Wallet(storage) except FileNotFoundError as e: if sys.version_info[:3] >= (3, 8, 0): msg = "Could not find module 'libusb-1.0.dll' (or one of its dependencies)." if msg in e.args[0]: pytest.xfail("libusb DLL could not be found") return raise e except OSError as e: if sys.version_info[:3] < (3, 8, 0): if "The specified module could not be found" in e.args[1]: pytest.xfail("libusb DLL could not be found") return raise e old_password = password password = "******" wallet.update_password(password, old_password) if "standard" in wallet_filename: is_bip39 = "bip39" in wallet_filename check_legacy_parent_of_standard_wallet(wallet, is_bip39=is_bip39, password=password) elif "imported_privkey" in wallet_filename: check_legacy_parent_of_imported_privkey_wallet(wallet) elif "imported_address" in wallet_filename: check_legacy_parent_of_imported_address_wallet(wallet) elif "multisig" in wallet_filename: check_legacy_parent_of_multisig_wallet(wallet) elif "hardware" in wallet_filename: check_legacy_parent_of_hardware_wallet(wallet) else: raise Exception(f"unrecognised wallet file {wallet_filename}") if "testnet" in wallet_filename: Net.set_to(SVMainnet)
def check_legacy_parent_of_imported_address_wallet(wallet: Wallet) -> None: assert len(wallet.get_accounts()) == 1 account: ImportedAddressAccount = wallet.get_accounts()[0] assert len(wallet.get_keystores()) == 0 assert len(account.get_keystores()) == 0
def run_and_get_wallet(self): vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit() hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) self.msg_label = QLabel('') vbox.addWidget(self.msg_label) hbox2 = QHBoxLayout() self.pw_e = PasswordLineEdit() self.pw_e.setMinimumWidth(200) self.pw_label = QLabel(_('Password') + ':') self.pw_label.setAlignment(Qt.AlignTop) hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self._set_standard_layout(vbox, title=_('ElectrumSV wallet'), back_text=_(MSG_BUTTON_CANCEL)) wallet_folder = os.path.dirname(self.storage.path) def on_choose(): path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def on_filename(filename): path = os.path.join(wallet_folder, filename) try: self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except IOError: self.storage = None self.next_button.setEnabled(False) if self.storage: if not self.storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False elif self.storage.file_exists() and self.storage.is_encrypted( ): msg = '\n'.join([ _("This file is encrypted."), _('Enter your password or choose another file.'), ]) pw = True else: msg = _("Press 'Next' to open this wallet.") pw = False else: msg = _('Cannot read file') pw = False self.msg_label.setText(msg) if pw: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() button.clicked.connect(on_choose) self.name_e.textChanged.connect(on_filename) n = os.path.basename(self.storage.path) self.name_e.setText(n) while True: if self.storage.file_exists() and not self.storage.is_encrypted(): break if self.loop.exec_() != 2: # 2 = next return if not self.storage.file_exists(): break if self.storage.file_exists() and self.storage.is_encrypted(): password = self.pw_e.text() try: self.storage.decrypt(password) self.pw_e.setText('') break except InvalidPassword as e: QMessageBox.information(None, _('Error'), str(e)) continue except Exception as e: logger.exception("decrypting storage") QMessageBox.information(None, _('Error'), str(e)) return path = self.storage.path if self.storage.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are not supported.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = (_('Your accounts have been moved to') + ':\n' + file_list + '\n\n' + _('Do you want to delete the old file') + ':\n' + path) if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return if self.storage.requires_upgrade(): self.hide() msg = _( "The format of your wallet '%s' must be upgraded for ElectrumSV. " "This change will not be backward compatible" % path) if not self.question(msg): return self.storage.upgrade() self.wallet = Wallet(self.storage) return self.wallet action = self.storage.get_action() if action and action != 'new': self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run self.run(action) return self.wallet self.wallet = Wallet(self.storage) return self.wallet
def wallet_widgets(self, wallet: Wallet, tab: QWidget) -> None: usechange_cb = QCheckBox(_('Use change addresses')) usechange_cb.setChecked(wallet.get_use_change()) usechange_cb.setEnabled(app_state.config.is_modifiable('use_change')) usechange_cb.setToolTip( _('Using a different change key each time improves your privacy by ' 'making it more difficult for others to analyze your transactions.' )) def on_usechange(state): usechange_result = state == Qt.Checked if wallet.get_use_change() != usechange_result: wallet.set_use_change(usechange_result) multiple_cb.setEnabled(wallet.get_use_change()) usechange_cb.stateChanged.connect(on_usechange) multiple_cb = QCheckBox(_('Use multiple change addresses')) multiple_cb.setChecked(wallet.get_multiple_change()) multiple_cb.setEnabled(wallet.get_use_change()) multiple_cb.setToolTip('\n'.join([ _('In some cases, use up to 3 change keys in order to break ' 'up large coin amounts and obfuscate the recipient key.'), _('This may result in higher transactions fees.') ])) def on_multiple(state): multiple = state == Qt.Checked if wallet.get_multiple_change() != multiple: wallet.set_multiple_change(multiple) multiple_cb.stateChanged.connect(on_multiple) options_box = QGroupBox() options_vbox = QVBoxLayout() options_box.setLayout(options_vbox) options_vbox.addWidget(usechange_cb) options_vbox.addWidget(multiple_cb) transaction_cache_size = wallet.get_cache_size_for_tx_bytedata() # nz_label = HelpLabel(_('Transaction Cache Size (MB)') + ':', # _("This allows setting a per-wallet limit on the amount of transaction data cached " # "in memory. A value of 0 will disable the cache, and setting low values can cause " # "wallet slowness due to continual fetching of transaction data from the database.")) nz_modifiable = app_state.config.is_modifiable( 'tx_bytedata_cache_size') nz = QSpinBox() nz.setAlignment(Qt.AlignRight) nz.setMinimum(MINIMUM_TXDATA_CACHE_SIZE_MB) nz.setMaximum(MAXIMUM_TXDATA_CACHE_SIZE_MB) nz.setValue(transaction_cache_size) nz.setEnabled(nz_modifiable) def on_nz(): value = nz.value() # This will not resize the cache, as we do not want to be doing it with every # change and some changes may be bad to actually put in place. wallet.set_cache_size_for_tx_bytedata(value) nz.valueChanged.connect(on_nz) tx_cache_layout = QHBoxLayout() tx_cache_layout.setSpacing(15) tx_cache_layout.addWidget(nz) tx_cache_layout.addWidget(QLabel(_("MiB"))) form = FormSectionWidget(minimum_label_width=120) form.add_row(_('Options'), options_box, True) form.add_row(_('Transaction Cache Size'), tx_cache_layout) vbox = QVBoxLayout() vbox.addWidget(form) vbox.addStretch(1) tab.setLayout(vbox)
def _load_wallet(self) -> None: wizard: WalletWizard = self.wizard() wallet_path = wizard.get_wallet_path() wizard.set_wallet(Wallet(WalletStorage(wallet_path)))
def check_parent_of_blank_wallet(wallet: Wallet) -> None: assert len(wallet.get_accounts()) == 0 parent_keystores = wallet.get_keystores() assert len(parent_keystores) == 0
def check_create_keys(wallet: Wallet, account_script_type: ScriptType) -> None: def check_rows(rows: List[KeyInstanceRow], script_type: ScriptType) -> None: for row in rows: assert isinstance(row.keyinstance_id, int) assert account.get_id() == row.account_id assert 1 == row.masterkey_id assert script_type == row.script_type assert DerivationType.BIP32_SUBPATH == row.derivation_type assert None is row.description accounts = wallet.get_accounts() assert len(accounts) == 1 account = accounts[0] assert [] == account.get_existing_fresh_keys(RECEIVING_SUBPATH) assert [] == account.get_existing_fresh_keys(CHANGE_SUBPATH) assert account_script_type == account.get_default_script_type() keyinstances: List[KeyInstanceRow] = [] keyinstance_ids: Set[int] = set() for count in (0, 1, 5): new_keyinstances = account.create_keys(count, RECEIVING_SUBPATH) assert count == len(new_keyinstances) check_rows(new_keyinstances, account_script_type) keyinstance_ids |= set(keyinstance.keyinstance_id for keyinstance in new_keyinstances) keyinstances.extend(new_keyinstances) assert len(keyinstance_ids) == len(keyinstances) assert [] == account.get_existing_fresh_keys(RECEIVING_SUBPATH) for count in (0, 1, 5): last_row = keyinstances[-1] last_index = account.get_derivation_path(last_row.keyinstance_id)[-1] next_index = account.get_next_derivation_index(RECEIVING_SUBPATH) assert next_index == last_index + 1 try: new_keyinstances = account.create_keys_until( RECEIVING_SUBPATH + (next_index + count - 1,)) except AssertionError: assert 0 == count continue assert 0 != count assert count == len(new_keyinstances) check_rows(new_keyinstances, account_script_type) keyinstance_ids |= set(keyinstance.keyinstance_id for keyinstance in new_keyinstances) keyinstances.extend(new_keyinstances) assert len(keyinstance_ids) == len(keyinstances) assert [] == account.get_existing_fresh_keys(RECEIVING_SUBPATH) keyinstance_batches: List[List[KeyInstanceRow]] = [] for count in (0, 1, 5): new_keyinstances = account.get_fresh_keys(RECEIVING_SUBPATH, count) assert count == len(new_keyinstances) assert new_keyinstances == account.get_existing_fresh_keys(RECEIVING_SUBPATH) check_rows(new_keyinstances, ScriptType.NONE) # Verify each batch includes the last batch and the extra created keys. if len(keyinstance_batches) > 0: last_keyinstances = keyinstance_batches[-1] assert last_keyinstances == new_keyinstances[:len(last_keyinstances)] keyinstance_batches.append(new_keyinstances)
def test_legacy_wallet_loading(storage_info: WalletStorageInfo) -> None: # When a wallet is composed of multiple files, we need to know which to load. wallet_filenames = [] if storage_info.kind != StorageKind.DATABASE: wallet_filenames.append(storage_info.filename) if storage_info.kind in (StorageKind.DATABASE, StorageKind.HYBRID): wallet_filenames.append(storage_info.filename + DATABASE_EXT) temp_dir = tempfile.mkdtemp() for _wallet_filename in wallet_filenames: source_wallet_path = os.path.join(TEST_WALLET_PATH, _wallet_filename) wallet_path = os.path.join(temp_dir, _wallet_filename) shutil.copyfile(source_wallet_path, wallet_path) wallet_filename = storage_info.filename wallet_path = os.path.join(temp_dir, wallet_filename) if "testnet" in wallet_filename: Net.set_to(SVTestnet) if storage_info.kind == StorageKind.HYBRID: pytest.xfail("old development database wallets not supported yet") password = None storage = WalletStorage(wallet_path) if "passworded" in wallet_filename: password = "******" text_store = storage.get_text_store() text_store.load_data(text_store.decrypt(password)) if "encrypted" in wallet_filename: password = "******" check_no_password = False storage.upgrade(password is not None, password) try: wallet = Wallet(storage) except OSError as e: if "is not a valid Win32 application" not in e.args[1]: raise e pytest.xfail("Missing libusb for this architecture") return old_password = password password = "******" wallet.update_password(password, old_password) if "standard" in wallet_filename: is_bip39 = "bip39" in wallet_filename check_legacy_parent_of_standard_wallet(wallet, is_bip39=is_bip39, password=password) elif "imported_privkey" in wallet_filename: check_legacy_parent_of_imported_privkey_wallet(wallet) elif "imported_address" in wallet_filename: check_legacy_parent_of_imported_address_wallet(wallet) elif "multisig" in wallet_filename: check_legacy_parent_of_multisig_wallet(wallet) elif "hardware" in wallet_filename: check_legacy_parent_of_hardware_wallet(wallet) else: raise Exception(f"unrecognised wallet file {wallet_filename}") if "testnet" in wallet_filename: Net.set_to(SVMainnet)
def _load_wallet(self) -> None: wizard: WalletWizard = self.wizard() if wizard.get_wallet() is not None: return None wallet_path = wizard.get_wallet_path() wizard.set_wallet(Wallet(WalletStorage(wallet_path)))
def run_non_RPC(config): cmdname = config.get('cmd') storage = WalletStorage(config.get_wallet_path()) if storage.file_exists(): sys.exit("Error: Remove the existing wallet first!") def password_dialog(): return prompt_password( "Password (hit return if you do not wish to encrypt your wallet):") if cmdname == 'restore': text = config.get('text').strip() passphrase = config.get('passphrase', '') password = password_dialog() if keystore.is_private(text) else None if keystore.is_address_list(text): wallet = ImportedAddressWallet.from_text(storage, text) elif keystore.is_private_key_list(text): wallet = ImportedPrivkeyWallet.from_text(storage, text, password) else: if keystore.is_seed(text): k = keystore.from_seed(text, passphrase, False) elif keystore.is_master_key(text): k = keystore.from_master_key(text) else: sys.exit("Error: Seed or key not recognized") if password: k.update_password(None, password) storage.put('keystore', k.dump()) storage.put('wallet_type', 'standard') storage.put('use_encryption', bool(password)) storage.write() wallet = Wallet(storage) if not config.get('offline'): network = Network(config) network.start() wallet.start_threads(network) print("Recovering wallet...") wallet.synchronize() wallet.wait_until_synchronized() msg = ("Recovery successful" if wallet.is_found() else "Found no history for this wallet") else: msg = ("This wallet was restored offline. " "It may contain more addresses than displayed.") print(msg) elif cmdname == 'create': password = password_dialog() passphrase = config.get('passphrase', '') seed_type = 'standard' seed = Mnemonic('en').make_seed(seed_type) k = keystore.from_seed(seed, passphrase, False) storage.put('keystore', k.dump()) storage.put('wallet_type', 'standard') wallet = Wallet(storage) wallet.update_password(None, password, True) wallet.synchronize() print("Your wallet generation seed is:\n\"%s\"" % seed) print("Please keep it in a safe place; if you lose it, " "you will not be able to restore your wallet.") wallet.storage.write() print("Wallet saved in '%s'" % wallet.storage.path) sys.exit(0)
class ElectrumGui: def __init__(self, config, daemon, plugins): self.config = config self.network = daemon.network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print("Wallet not found. try 'electrum-sv create'") exit() if storage.is_encrypted(): password = getpass.getpass('Password:'******'updated', 'banner']) self.commands = [ _("[h] - displays this help text"), _("[i] - display transaction history"), _("[o] - enter payment order"), _("[p] - print stored payment order"), _("[s] - send stored payment order"), _("[r] - show own receipt addresses"), _("[c] - display contacts"), _("[b] - print server banner"), _("[q] - quit"), ] self.num_commands = len(self.commands) def on_network(self, event, *args): if event == 'updated': self.updated() elif event == 'banner': self.print_banner() def main_command(self): self.print_balance() c = input("enter command: ") if c == "h": self.print_commands() elif c == "i": self.print_history() elif c == "o": self.enter_order() elif c == "p": self.print_order() elif c == "s": self.send_order() elif c == "r": self.print_addresses() elif c == "c": self.print_contacts() elif c == "b": self.print_banner() elif c == "n": self.network_dialog() elif c == "e": self.settings_dialog() elif c == "q": self.done = 1 else: self.print_commands() def updated(self): s = self.get_balance() if s != self.last_balance: print(s) self.last_balance = s return True def print_commands(self): self.print_list(self.commands, "Available commands") def print_history(self): width = [20, 40, 14, 14] delta = (80 - sum(width) - 4) / 3 format_str = ("%" + "%d" % width[0] + "s" + "%" + "%d" % (width[1] + delta) + "s" + "%" + "%d" % (width[2] + delta) + "s" + "%" + "%d" % (width[3] + delta) + "s") messages = [] for item in self.wallet.get_history(): tx_hash, height, conf, timestamp, delta, balance = item if conf: try: time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] except Exception: time_str = "unknown" else: time_str = 'unconfirmed' label = self.wallet.get_label(tx_hash) messages.append( format_str % (time_str, label, format_satoshis(delta, whitespaces=True), format_satoshis(balance, whitespaces=True))) self.print_list( messages[::-1], format_str % (_("Date"), _("Description"), _("Amount"), _("Balance"))) def print_balance(self): print(self.get_balance()) def get_balance(self): if self.wallet.network.is_connected(): if not self.wallet.up_to_date: msg = _("Synchronizing...") else: c, u, x = self.wallet.get_balance() msg = _("Balance") + ": %f " % (Decimal(c) / COIN) if u: msg += " [%f unconfirmed]" % (Decimal(u) / COIN) if x: msg += " [%f unmatured]" % (Decimal(x) / COIN) else: msg = _("Not connected") return msg def print_contacts(self): messages = [ "%20s %45s " % (x[0], x[1][1]) for x in self.contacts.items() ] self.print_list(messages, "%19s %25s " % ("Key", "Value")) def print_addresses(self): messages = [ "%30s %30s " % (addr, self.wallet.labels.get(addr, "")) for addr in self.wallet.get_addresses() ] self.print_list(messages, "%19s %25s " % ("Address", "Label")) def print_order(self): print("send order to " + self.str_recipient + ", amount: " + self.str_amount + "\nfee: " + self.str_fee + ", desc: " + self.str_description) def enter_order(self): self.str_recipient = input("Pay to: ") self.str_description = input("Description : ") self.str_amount = input("Amount: ") self.str_fee = input("Fee: ") def send_order(self): self.do_send() def print_banner(self): for i, x in enumerate(self.wallet.network.banner.split('\n')): print(x) def print_list(self, lst, firstline): lst = list(lst) self.maxpos = len(lst) if not self.maxpos: return print(firstline) for i in range(self.maxpos): msg = lst[i] if i < len(lst) else "" print(msg) def main(self): while self.done == 0: self.main_command() def do_send(self): if not Address.is_valid(self.str_recipient): print(_('Invalid Bitcoin address')) return try: amount = int(Decimal(self.str_amount) * COIN) except Exception: print(_('Invalid Amount')) return try: fee = int(Decimal(self.str_fee) * COIN) except Exception: print(_('Invalid Fee')) return if self.wallet.has_password(): password = self.password_dialog() if not password: return else: password = None c = "" while c != "y": c = input("ok to send (y/n)?") if c == "n": return try: tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee) except Exception as e: print(str(e)) return if self.str_description: self.wallet.labels[tx.txid()] = self.str_description print(_("Please wait...")) status, msg = self.network.broadcast_transaction(tx) if status: print(_('Payment sent.')) #self.do_clear() #self.update_contacts_tab() else: print(_('Error')) def network_dialog(self): print( "use 'electrum-sv setconfig server/proxy' to change your network settings" ) return True def settings_dialog(self): print("use 'electrum-sv setconfig' to change your settings") return True def password_dialog(self): return getpass.getpass() # XXX unused def run_receive_tab(self, c): #if c == 10: # out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"]) return def run_contacts_tab(self, c): pass