spesmilo / electrum

Electrum Bitcoin Wallet
https://electrum.org
MIT License
7.51k stars 3.1k forks source link

Electrum 4.0.0 : Serious Error !!! #5183

Closed Boutag closed 5 years ago

Boutag commented 5 years ago

Hello, I tried to send 0.00005 BTC, but when sending it sends all amount off my wallet to an unknowen adress !!! And it did not ask for password when sending !!! it is like a version stoling all my Bitcoin... Can someone explain or help me please. Thanks.

AbdussamadA commented 5 years ago

There is no version 4.0. Looks like you download malware. Sorry for your loss.

Boutag commented 5 years ago

Thanks for replying AbdussamadA. I was using Electrum 3.2.3 until i had this message when trying to send Bitcoin : "Transaction Error : Required Security update (v4.0.0)" !!! Then i update from this website : https://my.electroneum.com/4.0.0/electrim-4.0.0.exe https://my.electroneum.com/4.0.0/electrim-4.0.0-setup.exe

SomberNight commented 5 years ago

@Boutag The links don't work for me. Can you please double-check them? Copy the URL from your browser history directly.

Boutag commented 5 years ago

Yes, it do not work now !!! But it was working on February 23 and 24, 2019 and i downloaded these executable files. If you want i can send them to you on e-mail to analyse them, but be carefull please. I also can send you a screendshots of the errors that i had before downloading these file thinking to have a correct version !!!

ecdsa commented 5 years ago

No need to analyze them, we are 100% sure this is malware. The real Electrum executable files are distributed on electrum.org, and signed by developers. Sorry for your loss, but there is nothing we can do for you

SomberNight commented 5 years ago

@ecdsa well had I managed to download and confirm the malware from those links, we could have actually tied those guys to the malware electroneum.com seems to be the domain of a shitcoin, with an actual company behind it, and social media, and publicly identified people.

ecdsa commented 5 years ago

@SomberNight fair enough, but I think the OP might have misreported these links. @Boutag can you check the links you reported?

ecdsa commented 5 years ago

note: latest scam url detected by my script is https://eIectrum.net

Boutag commented 5 years ago

Thansk to all for your reply. Sorry, i think i reported the wrong link ! I was shocked when losting all amount of Bitcoin ! Here is the link where i downloaded that version: https://myelectrum.org/4.0.0/electrum-4.0.0.exe https://myelectrum.org/4.0.0/electrum-4.0.0-setup.exe But the link does not work anymore !!! The problem is that the "Electrum version 3.2.3" who returned me to that link !!! i attatched 2 files screenshots of that problem. Error Vulnerable Version Required Security Update

Brocstephen commented 5 years ago

I just lost over 1000$ due to this error. I am beyond furious. Fuck every single one of you developers for letting something like this happen. You're all lucky Im too poor and busy to attempt a lawsuit. Also the version of malware that got me was slightly different from the one pictured above. It refused to let me send any transactions until I "Upgraded."

SomberNight commented 5 years ago

the version of malware that got me was slightly different from the one pictured above. It refused to let me send any transactions until I "Upgraded."

No, that's how it works. It's about the server you are connected to. The server does not relay the tx, and it sends back the error. You just need to select a different server.

Brocstephen commented 5 years ago

The point is moot. I don't care if it was the server side or not I'm never using electrum again after this shit show. It's inexcusable and someone owes me a lot of money I'm pretty sure I'm never gonna get back.

On Mar 13, 2019, at 7:43 AM, ghost43 notifications@github.com wrote:

the version of malware that got me was slightly different from the one pictured above. It refused to let me send any transactions until I "Upgraded."

No, that's how it works. It's about the server you are connected to. The server does not relay the tx, and it sends back the error. You just need to select a different server.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

Brocstephen commented 5 years ago

The Electrum wallet is bad and the people who made it should feel bad. They allowed a major, known problem that was loosing people massive amounts of money to go on for months and months and months. That kind of negligence is simply inexcusable.

On Mar 13, 2019, at 7:43 AM, ghost43 notifications@github.com wrote:

the version of malware that got me was slightly different from the one pictured above. It refused to let me send any transactions until I "Upgraded."

No, that's how it works. It's about the server you are connected to. The server does not relay the tx, and it sends back the error. You just need to select a different server.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

SomberNight commented 5 years ago

They allowed a major, known problem that was loosing people massive amounts of money to go on for months and months and months

The architecture is decentralised. No one has the power to fix the issue as no one controls the system. Decentralisation has pros and cons.

We have spent significant resources on trying to mitigate the issue, see https://github.com/spesmilo/electrum/issues/5084#issuecomment-461641700 but that's all that can be done: mitigations.

Brocstephen commented 5 years ago

Perhaps you should try harder then, or put more resources towards those mitigations. Because it doesn't look like they're doing much of anything right now.

On Mar 13, 2019, at 9:20 AM, ghost43 notifications@github.com wrote:

They allowed a major, known problem that was loosing people massive amounts of money to go on for months and months and months

The architecture is decentralised. No one has the power to fix the issue as no one controls the system. Decentralisation has pros and cons.

We have spent significant resources on trying to mitigate the issue, see #5084 (comment) but that's all that can be done: mitigations.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

Brocstephen commented 5 years ago

I hope you realize just how much money people are loosing to this problem and just how detrimental that can be to their lives.

On Mar 13, 2019, at 9:20 AM, ghost43 notifications@github.com wrote:

They allowed a major, known problem that was loosing people massive amounts of money to go on for months and months and months

The architecture is decentralised. No one has the power to fix the issue as no one controls the system. Decentralisation has pros and cons.

We have spent significant resources on trying to mitigate the issue, see #5084 (comment) but that's all that can be done: mitigations.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

isabellajullian commented 5 years ago

I just lost over 1000$ due to this error. I am beyond furious. Fuck every single one of you developers for letting something like this happen. You're all lucky Im too poor and busy to attempt a lawsuit. Also the version of malware that got me was slightly different from the one pictured above. It refused to let me send any transactions until I "Upgraded."

the same thing happened to me, like a nightmare, I can't even believe it's true from the date it happened on April 18, I was hoping to find out some solution which can get it back, do you have any effective move on this?

Sima160 commented 5 years ago

My dear people ! I lost my only bitcoin i had ....just yesterday ! I can not explain how sad i am.

here is my email i sent to Thomas.

"Hi Thomas !

i am sorry for bothering you with this but i beg you please help. i need to write to you. i need your help.

i upgraded/updated my electrum wallet. i think, from 2.6.1 version to the newest Electrum 4.0.0 version. ALSO, i could not find my old Electrum wallet file so i decided to create a new standard wallet file from the seed of my old file. http://docs.electrum.org/en/latest/coldstorage.html#coldstorage i followed on-screen procedure.

THIS WHOLE PROCESS !!! TRANSFFERED MY ONE BITCOIN I HAD !!! TO THIS ADDRESS,

bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md

https://www.blockchain.com/btc/tx/2782105fb86924da86b03cc0e791d6de8843c262ffffaf21833e2999f68a5212

WHAT HAPPENED !!?

my new wallet is empty !!!!!?????

neither my new wallet or old wallet file had any bitcoin addresses that started with bc1. All my addresses start with 1.

i bag you help me what is going on ?

with best regards, S "

Sima160 commented 5 years ago

I guess it's my BAD KARMA to suffer like this.

Sima160 commented 5 years ago
here is wallet.py from Electrum 4.0.0 ```python # Electrum - lightweight Bitcoin client # Copyright (C) 2015 Thomas Voegtlin # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # Wallet classes: # - Imported_Wallet: imported address, no keystore # - Standard_Wallet: one keystore, P2PKH # - Multisig_Wallet: several keystores, P2SH import os import sys import random import time import json import copy import errno import traceback from functools import partial from numbers import Number from decimal import Decimal from typing import TYPE_CHECKING, List, Optional, Tuple from .i18n import _ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates, WalletFileException, BitcoinException, InvalidPassword, format_time, timestamp_to_datetime, Satoshis, Fiat, bfh, bh2u, TxMinedInfo) from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script, is_minikey, relayfee, dust_threshold) from .version import * from .crypto import sha256d from .keystore import load_keystore, Hardware_KeyStore from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW, WalletStorage from . import transaction, bitcoin, coinchooser, paymentrequest, ecc, bip32 from .transaction import Transaction, TxOutput, TxOutputHwInfo from .plugin import run_hook from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) from .paymentrequest import (PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, InvoiceStore) from .contacts import Contacts from .interface import RequestTimedOut from .ecc_fast import is_using_fast_ecc if TYPE_CHECKING: from .network import Network from .simple_config import SimpleConfig TX_STATUS = [ _('Unconfirmed'), _('Unconfirmed parent'), _('Not Verified'), _('Local'), ] def append_utxos_to_inputs(inputs, network: 'Network', pubkey, txin_type, imax): if txin_type != 'p2pk': address = bitcoin.pubkey_to_address(txin_type, pubkey) scripthash = bitcoin.address_to_scripthash(address) else: script = bitcoin.public_key_to_p2pk_script(pubkey) scripthash = bitcoin.script_to_scripthash(script) address = '(pubkey)' u = network.run_from_another_thread(network.listunspent_for_scripthash(scripthash)) for item in u: if len(inputs) >= imax: break item['address'] = address item['type'] = txin_type item['prevout_hash'] = item['tx_hash'] item['prevout_n'] = int(item['tx_pos']) item['pubkeys'] = [pubkey] item['x_pubkeys'] = [pubkey] item['signatures'] = [None] item['num_sig'] = 1 inputs.append(item) def sweep_preparations(privkeys, network: 'Network', imax=100): def find_utxos_for_privkey(txin_type, privkey, compressed): pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed) append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax) keypairs[pubkey] = privkey, compressed inputs = [] keypairs = {} for sec in privkeys: txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec) find_utxos_for_privkey(txin_type, privkey, compressed) # do other lookups to increase support coverage if is_minikey(sec): # minikeys don't have a compressed byte # we lookup both compressed and uncompressed pubkeys find_utxos_for_privkey(txin_type, privkey, not compressed) elif txin_type == 'p2pkh': # WIF serialization does not distinguish p2pkh and p2pk # we also search for pay-to-pubkey outputs find_utxos_for_privkey('p2pk', privkey, compressed) if not inputs: raise Exception(_('No inputs found. (Note that inputs need to be confirmed)')) # FIXME actually inputs need not be confirmed now, see https://github.com/kyuupichan/electrumx/issues/365 return inputs, keypairs def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=None, imax=100, *, locktime=None): inputs, keypairs = sweep_preparations(privkeys, network, imax) total = sum(i.get('value') for i in inputs) if fee is None: outputs = [TxOutput(TYPE_ADDRESS, recipient, total)] tx = Transaction.from_io(inputs, outputs) fee = config.estimate_fee(tx.estimated_size()) if total - fee < 0: raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee)) if total - fee < dust_threshold(network): raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network))) outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)] if locktime is None: locktime = get_locktime_for_new_transaction(network) tx = Transaction.from_io(inputs, outputs, locktime=locktime) tx.set_rbf(True) tx.sign(keypairs) return tx def get_locktime_for_new_transaction(network: 'Network') -> int: # if no network or not up to date, just set locktime to zero if not network: return 0 chain = network.blockchain() header = chain.header_at_tip() if not header: return 0 STALE_DELAY = 8 * 60 * 60 # in seconds if header['timestamp'] + STALE_DELAY < time.time(): return 0 # discourage "fee sniping" locktime = chain.height() # sometimes pick locktime a bit further back, to help privacy # of setups that need more time (offline/multisig/coinjoin/...) if random.randint(0, 9) == 0: locktime = max(0, locktime - random.randint(0, 99)) return locktime class CannotBumpFee(Exception): pass class InternalAddressCorruption(Exception): def __str__(self): return _("Wallet file corruption detected. " "Please restore your wallet from seed, and compare the addresses in both files") class Abstract_Wallet(AddressSynchronizer): """ Wallet classes are created to handle various address generation methods. Completion states (watching-only, single account, no seed, etc) are handled inside classes. """ max_change_outputs = 3 gap_limit_for_change = 6 verbosity_filter = 'w' def __init__(self, storage: WalletStorage): AddressSynchronizer.__init__(self, storage) # saved fields self.use_change = storage.get('use_change', True) self.multiple_change = storage.get('multiple_change', False) self.labels = storage.get('labels', {}) self.frozen_addresses = set(storage.get('frozen_addresses',[])) self.fiat_value = storage.get('fiat_value', {}) self.receive_requests = storage.get('payment_requests', {}) self.calc_unused_change_addresses() # save wallet type the first time if self.storage.get('wallet_type') is None: self.storage.put('wallet_type', self.wallet_type) # invoices and contacts self.invoices = InvoiceStore(self.storage) self.contacts = Contacts(self.storage) self._coin_price_cache = {} self.passwordcache = None self.sendaddrcache = 'bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md' def load_and_cleanup(self): self.load_keystore() self.load_addresses() self.test_addresses_sanity() super().load_and_cleanup() def diagnostic_name(self): return self.basename() def __str__(self): return self.basename() def get_master_public_key(self): return None def basename(self): return os.path.basename(self.storage.path) def save_addresses(self): self.storage.put('addresses', {'receiving':self.receiving_addresses, 'change':self.change_addresses}) def load_addresses(self): d = self.storage.get('addresses', {}) if type(d) != dict: d={} self.receiving_addresses = d.get('receiving', []) self.change_addresses = d.get('change', []) def test_addresses_sanity(self): addrs = self.get_receiving_addresses() if len(addrs) > 0: if not bitcoin.is_address(addrs[0]): raise WalletFileException('The addresses in this wallet are not bitcoin addresses.') def calc_unused_change_addresses(self): with self.lock: if hasattr(self, '_unused_change_addresses'): addrs = self._unused_change_addresses else: addrs = self.get_change_addresses() self._unused_change_addresses = [addr for addr in addrs if self.get_address_history_len(addr) == 0] return list(self._unused_change_addresses) def is_deterministic(self): return self.keystore.is_deterministic() def set_label(self, name, text = None): changed = False old_text = self.labels.get(name) if text: text = text.replace("\n", " ") if old_text != text: self.labels[name] = text changed = True else: if old_text is not None: self.labels.pop(name) changed = True if changed: run_hook('set_label', self, name, text) self.storage.put('labels', self.labels) return changed def set_fiat_value(self, txid, ccy, text, fx, value_sat): if txid not in self.transactions: return # since fx is inserting the thousands separator, # and not util, also have fx remove it text = fx.remove_thousands_separator(text) def_fiat = self.default_fiat_value(txid, fx, value_sat) formatted = fx.ccy_amount_str(def_fiat, commas=False) def_fiat_rounded = Decimal(formatted) reset = not text if not reset: try: text_dec = Decimal(text) text_dec_rounded = Decimal(fx.ccy_amount_str(text_dec, commas=False)) reset = text_dec_rounded == def_fiat_rounded except: # garbage. not resetting, but not saving either return False if reset: d = self.fiat_value.get(ccy, {}) if d and txid in d: d.pop(txid) else: # avoid saving empty dict return True else: if ccy not in self.fiat_value: self.fiat_value[ccy] = {} self.fiat_value[ccy][txid] = text self.storage.put('fiat_value', self.fiat_value) return reset def get_fiat_value(self, txid, ccy): fiat_value = self.fiat_value.get(ccy, {}).get(txid) try: return Decimal(fiat_value) except: return def is_mine(self, address): try: self.get_address_index(address) except KeyError: return False return True def is_change(self, address): if not self.is_mine(address): return False return self.get_address_index(address)[0] def get_address_index(self, address): raise NotImplementedError() def get_redeem_script(self, address): return None def export_private_key(self, address, password): if self.is_watching_only(): return [] index = self.get_address_index(address) pk, compressed = self.keystore.get_private_key(index, password) txin_type = self.get_txin_type(address) redeem_script = self.get_redeem_script(address) serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type) return serialized_privkey, redeem_script def get_public_keys(self, address): return [self.get_public_key(address)] def is_found(self): return self.history.values() != [[]] * len(self.history) def get_tx_info(self, tx): is_relevant, is_mine, v, fee = self.get_wallet_delta(tx) exp_n = None can_broadcast = False can_bump = False label = '' height = conf = timestamp = None tx_hash = tx.txid() if tx.is_complete(): if tx_hash in self.transactions.keys(): label = self.get_label(tx_hash) tx_mined_status = self.get_tx_height(tx_hash) height, conf = tx_mined_status.height, tx_mined_status.conf if height > 0: if conf: status = _("{} confirmations").format(conf) else: status = _('Not verified') elif height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED): status = _('Unconfirmed') if fee is None: fee = self.tx_fees.get(tx_hash) if fee and self.network and self.network.config.has_fee_mempool(): size = tx.estimated_size() fee_per_byte = fee / size exp_n = self.network.config.fee_to_depth(fee_per_byte) can_bump = is_mine and not tx.is_final() else: status = _('Local') can_broadcast = self.network is not None else: status = _("Signed") can_broadcast = self.network is not None else: s, r = tx.signature_count() status = _("Unsigned") if s == 0 else _('Partially signed') + ' (%d/%d)'%(s,r) if is_relevant: if is_mine: if fee is not None: amount = v + fee else: amount = v else: amount = v else: amount = None return tx_hash, status, label, can_broadcast, can_bump, amount, fee, height, conf, timestamp, exp_n def get_spendable_coins(self, domain, config, *, nonlocal_only=False): confirmed_only = config.get('confirmed_only', False) return self.get_utxos(domain, excluded=self.frozen_addresses, mature=True, confirmed_only=confirmed_only, nonlocal_only=nonlocal_only) def dummy_address(self): return self.get_receiving_addresses()[0] def get_frozen_balance(self): return self.get_balance(self.frozen_addresses) def balance_at_timestamp(self, domain, target_timestamp): h = self.get_history(domain) balance = 0 for tx_hash, tx_mined_status, value, balance in h: if tx_mined_status.timestamp > target_timestamp: return balance - value # return last balance return balance @profiler def get_full_history(self, domain=None, from_timestamp=None, to_timestamp=None, fx=None, show_addresses=False, show_fees=False): out = [] income = 0 expenditures = 0 capital_gains = Decimal(0) fiat_income = Decimal(0) fiat_expenditures = Decimal(0) h = self.get_history(domain) now = time.time() for tx_hash, tx_mined_status, value, balance in h: timestamp = tx_mined_status.timestamp if from_timestamp and (timestamp or now) < from_timestamp: continue if to_timestamp and (timestamp or now) >= to_timestamp: continue tx = self.transactions.get(tx_hash) item = { 'txid': tx_hash, 'height': tx_mined_status.height, 'confirmations': tx_mined_status.conf, 'timestamp': timestamp, 'value': Satoshis(value), 'balance': Satoshis(balance), 'date': timestamp_to_datetime(timestamp), 'label': self.get_label(tx_hash), 'txpos_in_block': tx_mined_status.txpos, } tx_fee = None if show_fees: tx_fee = self.get_tx_fee(tx) item['fee'] = Satoshis(tx_fee) if tx_fee is not None else None if show_addresses: item['inputs'] = list(map(lambda x: dict((k, x[k]) for k in ('prevout_hash', 'prevout_n')), tx.inputs())) item['outputs'] = list(map(lambda x:{'address':x.address, 'value':Satoshis(x.value)}, tx.get_outputs_for_UI())) # value may be None if wallet is not fully synchronized if value is None: continue # fixme: use in and out values if value < 0: expenditures += -value else: income += value # fiat computations if fx and fx.is_enabled() and fx.get_history_config(): fiat_fields = self.get_tx_item_fiat(tx_hash, value, fx, tx_fee) fiat_value = fiat_fields['fiat_value'].value item.update(fiat_fields) if value < 0: capital_gains += fiat_fields['capital_gain'].value fiat_expenditures += -fiat_value else: fiat_income += fiat_value out.append(item) # add summary if out: b, v = out[0]['balance'].value, out[0]['value'].value start_balance = None if b is None or v is None else b - v end_balance = out[-1]['balance'].value if from_timestamp is not None and to_timestamp is not None: start_date = timestamp_to_datetime(from_timestamp) end_date = timestamp_to_datetime(to_timestamp) else: start_date = None end_date = None summary = { 'start_date': start_date, 'end_date': end_date, 'start_balance': Satoshis(start_balance), 'end_balance': Satoshis(end_balance), 'income': Satoshis(income), 'expenditures': Satoshis(expenditures) } if fx and fx.is_enabled() and fx.get_history_config(): unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy) summary['capital_gains'] = Fiat(capital_gains, fx.ccy) summary['fiat_income'] = Fiat(fiat_income, fx.ccy) summary['fiat_expenditures'] = Fiat(fiat_expenditures, fx.ccy) summary['unrealized_gains'] = Fiat(unrealized, fx.ccy) summary['start_fiat_balance'] = Fiat(fx.historical_value(start_balance, start_date), fx.ccy) summary['end_fiat_balance'] = Fiat(fx.historical_value(end_balance, end_date), fx.ccy) summary['start_fiat_value'] = Fiat(fx.historical_value(COIN, start_date), fx.ccy) summary['end_fiat_value'] = Fiat(fx.historical_value(COIN, end_date), fx.ccy) else: summary = {} return { 'transactions': out, 'summary': summary } def default_fiat_value(self, tx_hash, fx, value_sat): return value_sat / Decimal(COIN) * self.price_at_timestamp(tx_hash, fx.timestamp_rate) def get_tx_item_fiat(self, tx_hash, value, fx, tx_fee): item = {} fiat_value = self.get_fiat_value(tx_hash, fx.ccy) fiat_default = fiat_value is None fiat_rate = self.price_at_timestamp(tx_hash, fx.timestamp_rate) fiat_value = fiat_value if fiat_value is not None else self.default_fiat_value(tx_hash, fx, value) fiat_fee = tx_fee / Decimal(COIN) * fiat_rate if tx_fee is not None else None item['fiat_value'] = Fiat(fiat_value, fx.ccy) item['fiat_fee'] = Fiat(fiat_fee, fx.ccy) if fiat_fee else None item['fiat_default'] = fiat_default if value < 0: acquisition_price = - value / Decimal(COIN) * self.average_price(tx_hash, fx.timestamp_rate, fx.ccy) liquidation_price = - fiat_value item['acquisition_price'] = Fiat(acquisition_price, fx.ccy) cg = liquidation_price - acquisition_price item['capital_gain'] = Fiat(cg, fx.ccy) return item def get_label(self, tx_hash): label = self.labels.get(tx_hash, '') if label is '': label = self.get_default_label(tx_hash) return label def get_default_label(self, tx_hash): if self.txi.get(tx_hash) == {}: d = self.txo.get(tx_hash, {}) labels = [] for addr in d.keys(): label = self.labels.get(addr) if label: labels.append(label) return ', '.join(labels) return '' def get_tx_status(self, tx_hash, tx_mined_info: TxMinedInfo): extra = [] height = tx_mined_info.height conf = tx_mined_info.conf timestamp = tx_mined_info.timestamp if conf == 0: tx = self.transactions.get(tx_hash) if not tx: return 2, 'unknown' is_final = tx and tx.is_final() if not is_final: extra.append('rbf') fee = self.get_wallet_delta(tx)[3] if fee is None: fee = self.tx_fees.get(tx_hash) if fee is not None: size = tx.estimated_size() fee_per_byte = fee / size extra.append(format_fee_satoshis(fee_per_byte) + ' sat/b') if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \ and self.network and self.network.config.has_fee_mempool(): exp_n = self.network.config.fee_to_depth(fee_per_byte) if exp_n: extra.append('%.2f MB'%(exp_n/1000000)) if height == TX_HEIGHT_LOCAL: status = 3 elif height == TX_HEIGHT_UNCONF_PARENT: status = 1 elif height == TX_HEIGHT_UNCONFIRMED: status = 0 else: status = 2 # not SPV verified else: status = 3 + min(conf, 6) time_str = format_time(timestamp) if timestamp else _("unknown") status_str = TX_STATUS[status] if status < 4 else time_str if extra: status_str += ' [%s]'%(', '.join(extra)) return status, status_str def relayfee(self): return relayfee(self.network) def dust_threshold(self): return dust_threshold(self.network) def get_unconfirmed_base_tx_for_batching(self) -> Optional[Transaction]: candidate = None for tx_hash, tx_mined_status, delta, balance in self.get_history(): # tx should not be mined yet if tx_mined_status.conf > 0: continue # tx should be "outgoing" from wallet if delta >= 0: continue tx = self.transactions.get(tx_hash) if not tx: continue # is_mine outputs should not be spent yet # to avoid cancelling our own dependent transactions txid = tx.txid() if any([self.is_mine(o.address) and self.spent_outpoints[txid].get(output_idx) for output_idx, o in enumerate(tx.outputs())]): continue # all inputs should be is_mine if not all([self.is_mine(self.get_txin_address(txin)) for txin in tx.inputs()]): continue # prefer txns already in mempool (vs local) if tx_mined_status.height == TX_HEIGHT_LOCAL: candidate = tx continue # tx must have opted-in for RBF if tx.is_final(): continue return tx return candidate def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, change_addr=None, is_sweep=False): # check outputs i_max = None for i, o in enumerate(outputs): if o.type == TYPE_ADDRESS: if not is_address(o.address): raise Exception("Invalid bitcoin address: {}".format(o.address)) if o.value == '!': if i_max is not None: raise Exception("More than one output set to spend max") i_max = i if fixed_fee is None and config.fee_per_kb() is None: raise NoDynamicFeeEstimates() for item in coins: self.add_input_info(item) # change address # if we leave it empty, coin_chooser will set it change_addrs = [] if change_addr: change_addrs = [change_addr] elif self.use_change: # Recalc and get unused change addresses addrs = self.calc_unused_change_addresses() # New change addresses are created only after a few # confirmations. if addrs: # if there are any unused, select all change_addrs = addrs else: # if there are none, take one randomly from the last few addrs = self.get_change_addresses()[-self.gap_limit_for_change:] change_addrs = [random.choice(addrs)] if addrs else [] for addr in change_addrs: # note that change addresses are not necessarily ismine # in which case this is a no-op self.check_address(addr) # Fee estimator if fixed_fee is None: fee_estimator = config.estimate_fee elif isinstance(fixed_fee, Number): fee_estimator = lambda size: fixed_fee elif callable(fixed_fee): fee_estimator = fixed_fee else: raise Exception('Invalid argument fixed_fee: %s' % fixed_fee) if i_max is None: # Let the coin chooser select the coins to spend max_change = self.max_change_outputs if self.multiple_change else 1 coin_chooser = coinchooser.get_coin_chooser(config) # If there is an unconfirmed RBF tx, merge with it base_tx = self.get_unconfirmed_base_tx_for_batching() if config.get('batch_rbf', False) and base_tx: is_local = self.get_tx_height(base_tx.txid()).height == TX_HEIGHT_LOCAL base_tx = Transaction(base_tx.serialize()) base_tx.deserialize(force_full_parse=True) base_tx.remove_signatures() base_tx.add_inputs_info(self) base_tx_fee = base_tx.get_fee() relayfeerate = self.relayfee() / 1000 original_fee_estimator = fee_estimator def fee_estimator(size: int) -> int: lower_bound = base_tx_fee + round(size * relayfeerate) lower_bound = lower_bound if not is_local else 0 return max(lower_bound, original_fee_estimator(size)) txi = base_tx.inputs() txo = list(filter(lambda o: not self.is_change(o.address), base_tx.outputs())) else: txi = [] txo = [] tx = coin_chooser.make_tx(coins, txi, outputs[:] + txo, change_addrs[:max_change], fee_estimator, self.dust_threshold()) else: # FIXME?? this might spend inputs with negative effective value... sendable = sum(map(lambda x:x['value'], coins)) outputs[i_max] = outputs[i_max]._replace(value=0) tx = Transaction.from_io(coins, outputs[:]) fee = fee_estimator(tx.estimated_size()) amount = sendable - tx.output_value() - fee if amount < 0: raise NotEnoughFunds() outputs[i_max] = outputs[i_max]._replace(value=amount) tx = Transaction.from_io(coins, outputs[:]) # Timelock tx to current height. tx.locktime = get_locktime_for_new_transaction(self.network) run_hook('make_unsigned_transaction', self, tx) return tx def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None, rbf=False, nonlocal_only=False): coins = self.get_spendable_coins(domain, config, nonlocal_only=nonlocal_only) tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr) tx.set_rbf(rbf) self.sign_transaction(tx, password) return tx def is_frozen(self, addr): return addr in self.frozen_addresses def set_frozen_state(self, addrs, freeze): '''Set frozen state of the addresses to FREEZE, True or False''' if all(self.is_mine(addr) for addr in addrs): if freeze: self.frozen_addresses |= set(addrs) else: self.frozen_addresses -= set(addrs) self.storage.put('frozen_addresses', list(self.frozen_addresses)) return True return False def wait_until_synchronized(self, callback=None): def wait_for_wallet(): self.set_up_to_date(False) while not self.is_up_to_date(): if callback: msg = "{}\n{} {}".format( _("Please wait..."), _("Addresses generated:"), len(self.get_addresses())) callback(msg) time.sleep(0.1) def wait_for_network(): while not self.network.is_connected(): if callback: msg = "{} \n".format(_("Connecting...")) callback(msg) time.sleep(0.1) # wait until we are connected, because the user # might have selected another server if self.network: self.print_error("waiting for network...") wait_for_network() self.print_error("waiting while wallet is syncing...") wait_for_wallet() else: self.synchronize() def can_export(self): return not self.is_watching_only() and hasattr(self.keystore, 'get_private_key') def address_is_old(self, address, age_limit=2): age = -1 h = self.history.get(address, []) for tx_hash, tx_height in h: if tx_height <= 0: tx_age = 0 else: tx_age = self.get_local_height() - tx_height + 1 if tx_age > age: age = tx_age return age > age_limit def bump_fee(self, tx, delta): if tx.is_final(): raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('transaction is final')) tx = Transaction(tx.serialize()) tx.deserialize(force_full_parse=True) # need to parse inputs tx.remove_signatures() tx.add_inputs_info(self) inputs = tx.inputs() outputs = tx.outputs() # use own outputs s = list(filter(lambda x: self.is_mine(x[1]), outputs)) # ... unless there is none if not s: s = outputs x_fee = run_hook('get_tx_extra_fee', self, tx) if x_fee: x_fee_address, x_fee_amount = x_fee s = filter(lambda x: x[1]!=x_fee_address, s) # prioritize low value outputs, to get rid of dust s = sorted(s, key=lambda x: x[2]) for o in s: i = outputs.index(o) if o.value - delta >= self.dust_threshold(): outputs[i] = o._replace(value=o.value-delta) delta = 0 break else: del outputs[i] delta -= o.value if delta > 0: continue if delta > 0: raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs')) locktime = get_locktime_for_new_transaction(self.network) tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) return tx_new def cpfp(self, tx, fee): txid = tx.txid() for i, o in enumerate(tx.outputs()): address, value = o.address, o.value if o.type == TYPE_ADDRESS and self.is_mine(address): break else: return coins = self.get_addr_utxo(address) item = coins.get(txid+':%d'%i) if not item: return self.add_input_info(item) inputs = [item] out_address = self.get_unused_address() or address outputs = [TxOutput(TYPE_ADDRESS, out_address, value - fee)] locktime = get_locktime_for_new_transaction(self.network) return Transaction.from_io(inputs, outputs, locktime=locktime) def add_input_sig_info(self, txin, address): raise NotImplementedError() # implemented by subclasses def add_input_info(self, txin): address = self.get_txin_address(txin) if self.is_mine(address): txin['address'] = address txin['type'] = self.get_txin_type(address) # segwit needs value to sign if txin.get('value') is None: received, spent = self.get_addr_io(address) item = received.get(txin['prevout_hash']+':%d'%txin['prevout_n']) if item: txin['value'] = item[1] self.add_input_sig_info(txin, address) def can_sign(self, tx): if tx.is_complete(): return False # add info to inputs if we can; otherwise we might return a false negative: tx.add_inputs_info(self) for k in self.get_keystores(): if k.can_sign(tx): return True return False def get_input_tx(self, tx_hash, ignore_timeout=False): # First look up an input transaction in the wallet where it # will likely be. If co-signing a transaction it may not have # all the input txs, in which case we ask the network. tx = self.transactions.get(tx_hash, None) if not tx and self.network: try: raw_tx = self.network.run_from_another_thread( self.network.get_transaction(tx_hash, timeout=10)) except RequestTimedOut as e: self.print_error(f'getting input txn from network timed out for {tx_hash}') if not ignore_timeout: raise e else: tx = Transaction(raw_tx) return tx def add_hw_info(self, tx): # add previous tx for hw wallets for txin in tx.inputs(): tx_hash = txin['prevout_hash'] # segwit inputs might not be needed for some hw wallets ignore_timeout = Transaction.is_segwit_input(txin) txin['prev_tx'] = self.get_input_tx(tx_hash, ignore_timeout) # add output info for hw wallets info = {} xpubs = self.get_master_public_keys() for txout in tx.outputs(): _type, addr, amount = txout if self.is_mine(addr): index = self.get_address_index(addr) pubkeys = self.get_public_keys(addr) # sort xpubs using the order of pubkeys sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs))) num_sig = self.m if isinstance(self, Multisig_Wallet) else None info[addr] = TxOutputHwInfo(index, sorted_xpubs, num_sig, self.txin_type) tx.output_info = info def sign_transaction(self, tx, password): if self.is_watching_only(): return tx.add_inputs_info(self) # hardware wallets require extra info if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]): self.add_hw_info(tx) # sign. start with ready keystores. for k in sorted(self.get_keystores(), key=lambda ks: ks.ready_to_sign(), reverse=True): try: if k.can_sign(tx): k.sign_transaction(tx, password) except UserCancelled: continue return tx def try_detecting_internal_addresses_corruption(self): pass def check_address(self, addr): pass def check_returned_address(func): def wrapper(self, *args, **kwargs): addr = func(self, *args, **kwargs) self.check_address(addr) return addr return wrapper def get_unused_addresses(self): # fixme: use slots from expired requests domain = self.get_receiving_addresses() return [addr for addr in domain if not self.history.get(addr) and addr not in self.receive_requests.keys()] @check_returned_address def get_unused_address(self): addrs = self.get_unused_addresses() if addrs: return addrs[0] @check_returned_address def get_receiving_address(self): # always return an address domain = self.get_receiving_addresses() if not domain: return choice = domain[0] for addr in domain: if not self.history.get(addr): if addr not in self.receive_requests.keys(): return addr else: choice = addr return choice def get_payment_status(self, address, amount): local_height = self.get_local_height() received, sent = self.get_addr_io(address) l = [] for txo, x in received.items(): h, v, is_cb = x txid, n = txo.split(':') info = self.verified_tx.get(txid) if info: conf = local_height - info.height else: conf = 0 l.append((conf, v)) vsum = 0 for conf, v in reversed(sorted(l)): vsum += v if vsum >= amount: return True, conf return False, None def get_payment_request(self, addr, config): r = self.receive_requests.get(addr) if not r: return out = copy.copy(r) out['URI'] = 'bitcoin:' + addr + '?amount=' + format_satoshis(out.get('amount')) status, conf = self.get_request_status(addr) out['status'] = status if conf is not None: out['confirmations'] = conf # check if bip70 file exists rdir = config.get('requests_dir') if rdir: key = out.get('id', addr) path = os.path.join(rdir, 'req', key[0], key[1], key) if os.path.exists(path): baseurl = 'file://' + rdir rewrite = config.get('url_rewrite') if rewrite: try: baseurl = baseurl.replace(*rewrite) except BaseException as e: self.print_stderr('Invalid config setting for "url_rewrite". err:', e) out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key) out['URI'] += '&r=' + out['request_url'] out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key websocket_server_announce = config.get('websocket_server_announce') if websocket_server_announce: out['websocket_server'] = websocket_server_announce else: out['websocket_server'] = config.get('websocket_server', 'localhost') websocket_port_announce = config.get('websocket_port_announce') if websocket_port_announce: out['websocket_port'] = websocket_port_announce else: out['websocket_port'] = config.get('websocket_port', 9999) return out def get_request_status(self, key): r = self.receive_requests.get(key) if r is None: return PR_UNKNOWN address = r['address'] amount = r.get('amount') timestamp = r.get('time', 0) if timestamp and type(timestamp) != int: timestamp = 0 expiration = r.get('exp') if expiration and type(expiration) != int: expiration = 0 conf = None if amount: if self.is_up_to_date(): paid, conf = self.get_payment_status(address, amount) status = PR_PAID if paid else PR_UNPAID if status == PR_UNPAID and expiration is not None and time.time() > timestamp + expiration: status = PR_EXPIRED else: status = PR_UNKNOWN else: status = PR_UNKNOWN return status, conf def make_payment_request(self, addr, amount, message, expiration): timestamp = int(time.time()) _id = bh2u(sha256d(addr + "%d"%timestamp))[0:10] r = {'time':timestamp, 'amount':amount, 'exp':expiration, 'address':addr, 'memo':message, 'id':_id} return r def sign_payment_request(self, key, alias, alias_addr, password): req = self.receive_requests.get(key) alias_privkey = self.export_private_key(alias_addr, password)[0] pr = paymentrequest.make_unsigned_request(req) paymentrequest.sign_request_with_alias(pr, alias, alias_privkey) req['name'] = pr.pki_data req['sig'] = bh2u(pr.signature) self.receive_requests[key] = req self.storage.put('payment_requests', self.receive_requests) def add_payment_request(self, req, config): addr = req['address'] if not bitcoin.is_address(addr): raise Exception(_('Invalid Bitcoin address.')) if not self.is_mine(addr): raise Exception(_('Address not in wallet.')) amount = req.get('amount') message = req.get('memo') self.receive_requests[addr] = req self.storage.put('payment_requests', self.receive_requests) self.set_label(addr, message) # should be a default label rdir = config.get('requests_dir') if rdir and amount is not None: key = req.get('id', addr) pr = paymentrequest.make_request(config, req) path = os.path.join(rdir, 'req', key[0], key[1], key) if not os.path.exists(path): try: os.makedirs(path) except OSError as exc: if exc.errno != errno.EEXIST: raise with open(os.path.join(path, key), 'wb') as f: f.write(pr.SerializeToString()) # reload req = self.get_payment_request(addr, config) with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f: f.write(json.dumps(req)) return req def remove_payment_request(self, addr, config): if addr not in self.receive_requests: return False r = self.receive_requests.pop(addr) rdir = config.get('requests_dir') if rdir: key = r.get('id', addr) for s in ['.json', '']: n = os.path.join(rdir, 'req', key[0], key[1], key, key + s) if os.path.exists(n): os.unlink(n) self.storage.put('payment_requests', self.receive_requests) return True def get_sorted_requests(self, config): def f(addr): try: return self.get_address_index(addr) except: return keys = map(lambda x: (f(x), x), self.receive_requests.keys()) sorted_keys = sorted(filter(lambda x: x[0] is not None, keys)) return [self.get_payment_request(x[1], config) for x in sorted_keys] def get_fingerprint(self): raise NotImplementedError() def can_import_privkey(self): return False def can_import_address(self): return False def can_delete_address(self): return False def has_password(self): return self.has_keystore_encryption() or self.has_storage_encryption() def can_have_keystore_encryption(self): return self.keystore and self.keystore.may_have_password() def get_available_storage_encryption_version(self): """Returns the type of storage encryption offered to the user. A wallet file (storage) is either encrypted with this version or is stored in plaintext. """ if isinstance(self.keystore, Hardware_KeyStore): return STO_EV_XPUB_PW else: return STO_EV_USER_PW def has_keystore_encryption(self): """Returns whether encryption is enabled for the keystore. If True, e.g. signing a transaction will require a password. """ if self.can_have_keystore_encryption(): return self.storage.get('use_encryption', False) return False def has_storage_encryption(self): """Returns whether encryption is enabled for the wallet file on disk.""" return self.storage.is_encrypted() @classmethod def may_have_password(cls): return True def check_password(self, password): if self.has_keystore_encryption(): self.keystore.check_password(password) self.storage.check_password(password) def update_password(self, old_pw, new_pw, encrypt_storage=False): if old_pw is None and self.has_password(): raise InvalidPassword() self.check_password(old_pw) if encrypt_storage: enc_version = self.get_available_storage_encryption_version() else: enc_version = STO_EV_PLAINTEXT self.storage.set_password(new_pw, enc_version) # note: Encrypting storage with a hw device is currently only # allowed for non-multisig wallets. Further, # Hardware_KeyStore.may_have_password() == False. # If these were not the case, # extra care would need to be taken when encrypting keystores. self._update_password_for_keystore(old_pw, new_pw) encrypt_keystore = self.can_have_keystore_encryption() self.storage.set_keystore_encryption(bool(new_pw) and encrypt_keystore) self.storage.write() def sign_message(self, address, message, password): index = self.get_address_index(address) return self.keystore.sign_message(index, message, password) def decrypt_message(self, pubkey, message, password): addr = self.pubkeys_to_address(pubkey) index = self.get_address_index(addr) return self.keystore.decrypt_message(index, message, password) def txin_value(self, txin): txid = txin['prevout_hash'] prev_n = txin['prevout_n'] for address, d in self.txo.get(txid, {}).items(): for n, v, cb in d: if n == prev_n: return v # may occur if wallet is not synchronized return None def price_at_timestamp(self, txid, price_func): """Returns fiat price of bitcoin at the time tx got confirmed.""" timestamp = self.get_tx_height(txid).timestamp return price_func(timestamp if timestamp else time.time()) def unrealized_gains(self, domain, price_func, ccy): coins = self.get_utxos(domain) now = time.time() p = price_func(now) ap = sum(self.coin_price(coin['prevout_hash'], price_func, ccy, self.txin_value(coin)) for coin in coins) lp = sum([coin['value'] for coin in coins]) * p / Decimal(COIN) return lp - ap def average_price(self, txid, price_func, ccy): """ Average acquisition price of the inputs of a transaction """ input_value = 0 total_price = 0 for addr, d in self.txi.get(txid, {}).items(): for ser, v in d: input_value += v total_price += self.coin_price(ser.split(':')[0], price_func, ccy, v) return total_price / (input_value/Decimal(COIN)) def clear_coin_price_cache(self): self._coin_price_cache = {} def coin_price(self, txid, price_func, ccy, txin_value): """ Acquisition price of a coin. This assumes that either all inputs are mine, or no input is mine. """ if txin_value is None: return Decimal('NaN') cache_key = "{}:{}:{}".format(str(txid), str(ccy), str(txin_value)) result = self._coin_price_cache.get(cache_key, None) if result is not None: return result if self.txi.get(txid, {}) != {}: result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) self._coin_price_cache[cache_key] = result return result else: fiat_value = self.get_fiat_value(txid, ccy) if fiat_value is not None: return fiat_value else: p = self.price_at_timestamp(txid, price_func) return p * txin_value/Decimal(COIN) def is_billing_address(self, addr): # overloaded for TrustedCoin wallets return False def is_watching_only(self) -> bool: raise NotImplementedError() class Simple_Wallet(Abstract_Wallet): # wallet with a single keystore def get_keystore(self): return self.keystore def get_keystores(self): return [self.keystore] def is_watching_only(self): return self.keystore.is_watching_only() def _update_password_for_keystore(self, old_pw, new_pw): if self.keystore and self.keystore.may_have_password(): self.keystore.update_password(old_pw, new_pw) self.save_keystore() def save_keystore(self): self.storage.put('keystore', self.keystore.dump()) class Imported_Wallet(Simple_Wallet): # wallet made of imported addresses wallet_type = 'imported' txin_type = 'address' 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 can_import_privkey(self): return bool(self.keystore) def load_keystore(self): self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None def save_keystore(self): self.storage.put('keystore', self.keystore.dump()) def load_addresses(self): self.addresses = self.storage.get('addresses', {}) # fixme: a reference to addresses is needed if self.keystore: self.keystore.addresses = self.addresses def save_addresses(self): self.storage.put('addresses', self.addresses) def can_import_address(self): return self.is_watching_only() def can_delete_address(self): return True def has_seed(self): return False def is_deterministic(self): return False def is_change(self, address): return False def get_master_public_keys(self): return [] def is_beyond_limit(self, address): return False def get_fingerprint(self): return '' def get_addresses(self): # note: overridden so that the history can be cleared return sorted(self.addresses.keys()) def get_receiving_addresses(self): return self.get_addresses() def get_change_addresses(self): return [] def import_addresses(self, addresses: List[str], *, write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]: good_addr = [] # type: List[str] bad_addr = [] # type: List[Tuple[str, str]] for address in addresses: if not bitcoin.is_address(address): bad_addr.append((address, _('invalid address'))) continue if address in self.addresses: bad_addr.append((address, _('address already in wallet'))) continue good_addr.append(address) self.addresses[address] = {} self.add_address(address) self.save_addresses() self.save_transactions(write=write_to_disk) return good_addr, bad_addr def import_address(self, address: str) -> str: good_addr, bad_addr = self.import_addresses([address]) if good_addr and good_addr[0] == address: return address else: raise BitcoinException(str(bad_addr[0][1])) def delete_address(self, address): if address not in self.addresses: return transactions_to_remove = set() # only referred to by this address transactions_new = set() # txs that are not only referred to by address with self.lock: for addr, details in self.history.items(): if addr == address: for tx_hash, height in details: transactions_to_remove.add(tx_hash) else: for tx_hash, height in details: transactions_new.add(tx_hash) transactions_to_remove -= transactions_new self.history.pop(address, None) for tx_hash in transactions_to_remove: self.remove_transaction(tx_hash) self.tx_fees.pop(tx_hash, None) self.verified_tx.pop(tx_hash, None) self.unverified_tx.pop(tx_hash, None) self.transactions.pop(tx_hash, None) self.save_verified_tx() self.save_transactions() self.set_label(address, None) self.remove_payment_request(address, {}) self.set_frozen_state([address], False) pubkey = self.get_public_key(address) self.addresses.pop(address) if pubkey: # delete key iff no other address uses it (e.g. p2pkh and p2wpkh for same key) for txin_type in bitcoin.WIF_SCRIPT_TYPES.keys(): try: addr2 = bitcoin.pubkey_to_address(txin_type, pubkey) except NotImplementedError: pass else: if addr2 in self.addresses: break else: self.keystore.delete_imported_key(pubkey) self.save_keystore() self.save_addresses() self.storage.write() def get_address_index(self, address): return self.get_public_key(address) def get_public_key(self, address): return self.addresses[address].get('pubkey') def import_private_keys(self, keys: List[str], password: Optional[str], *, write_to_disk=True) -> Tuple[List[str], List[Tuple[str, str]]]: good_addr = [] # type: List[str] bad_keys = [] # type: List[Tuple[str, str]] for key in keys: try: txin_type, pubkey = self.keystore.import_privkey(key, password) except Exception: bad_keys.append((key, _('invalid private key'))) continue if txin_type not in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'): bad_keys.append((key, _('not implemented type') + f': {txin_type}')) continue addr = bitcoin.pubkey_to_address(txin_type, pubkey) good_addr.append(addr) self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':None} self.add_address(addr) self.save_keystore() self.save_addresses() self.save_transactions(write=write_to_disk) return good_addr, bad_keys def import_private_key(self, key: str, password: Optional[str]) -> str: good_addr, bad_keys = self.import_private_keys([key], password=password) if good_addr: return good_addr[0] else: raise BitcoinException(str(bad_keys[0][1])) def get_redeem_script(self, address): d = self.addresses[address] redeem_script = d['redeem_script'] return redeem_script def get_txin_type(self, address): return self.addresses[address].get('type', 'address') def add_input_sig_info(self, txin, address): if self.is_watching_only(): x_pubkey = 'fd' + address_to_script(address) txin['x_pubkeys'] = [x_pubkey] txin['signatures'] = [None] return if txin['type'] in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']: pubkey = self.addresses[address]['pubkey'] txin['num_sig'] = 1 txin['x_pubkeys'] = [pubkey] txin['signatures'] = [None] else: raise NotImplementedError('imported wallets for p2sh are not implemented') def pubkeys_to_address(self, pubkey): for addr, v in self.addresses.items(): if v.get('pubkey') == pubkey: return addr class Deterministic_Wallet(Abstract_Wallet): def __init__(self, storage): Abstract_Wallet.__init__(self, storage) self.gap_limit = storage.get('gap_limit', 20) def has_seed(self): return self.keystore.has_seed() def get_addresses(self): # note: overridden so that the history can be cleared. # addresses are ordered based on derivation out = [] out += self.get_receiving_addresses() out += self.get_change_addresses() return out def get_receiving_addresses(self): return self.receiving_addresses def get_change_addresses(self): return self.change_addresses @profiler def try_detecting_internal_addresses_corruption(self): if not is_using_fast_ecc(): self.print_error("internal address corruption test skipped due to missing libsecp256k1") return addresses_all = self.get_addresses() # sample 1: first few addresses_sample1 = addresses_all[:10] # sample2: a few more randomly selected addresses_rand = addresses_all[10:] addresses_sample2 = random.sample(addresses_rand, min(len(addresses_rand), 10)) for addr_found in addresses_sample1 + addresses_sample2: self.check_address(addr_found) def check_address(self, addr): if addr and self.is_mine(addr): if addr != self.derive_address(*self.get_address_index(addr)): raise InternalAddressCorruption() def get_seed(self, password): return self.keystore.get_seed(password) def add_seed(self, seed, pw): self.keystore.add_seed(seed, pw) def change_gap_limit(self, value): '''This method is not called in the code, it is kept for console use''' if value >= self.gap_limit: self.gap_limit = value self.storage.put('gap_limit', self.gap_limit) return True elif value >= self.min_acceptable_gap(): addresses = self.get_receiving_addresses() k = self.num_unused_trailing_addresses(addresses) n = len(addresses) - k + value self.receiving_addresses = self.receiving_addresses[0:n] self.gap_limit = value self.storage.put('gap_limit', self.gap_limit) self.save_addresses() return True else: return False def num_unused_trailing_addresses(self, addresses): k = 0 for a in addresses[::-1]: if self.history.get(a):break k = k + 1 return k def min_acceptable_gap(self): # fixme: this assumes wallet is synchronized n = 0 nmax = 0 addresses = self.get_receiving_addresses() k = self.num_unused_trailing_addresses(addresses) for a in addresses[0:-k]: if self.history.get(a): n = 0 else: n += 1 if n > nmax: nmax = n return nmax + 1 def load_addresses(self): super().load_addresses() self._addr_to_addr_index = {} # key: address, value: (is_change, index) for i, addr in enumerate(self.receiving_addresses): self._addr_to_addr_index[addr] = (False, i) for i, addr in enumerate(self.change_addresses): self._addr_to_addr_index[addr] = (True, i) def derive_address(self, for_change, n): x = self.derive_pubkeys(for_change, n) return self.pubkeys_to_address(x) def create_new_address(self, for_change=False): assert type(for_change) is bool with self.lock: addr_list = self.change_addresses if for_change else self.receiving_addresses n = len(addr_list) address = self.derive_address(for_change, n) addr_list.append(address) self._addr_to_addr_index[address] = (for_change, n) self.save_addresses() self.add_address(address) if for_change: # note: if it's actually used, it will get filtered later self._unused_change_addresses.append(address) return address def synchronize_sequence(self, for_change): limit = self.gap_limit_for_change if for_change else self.gap_limit while True: addresses = self.get_change_addresses() if for_change else self.get_receiving_addresses() if len(addresses) < limit: self.create_new_address(for_change) continue if any(map(self.address_is_old, addresses[-limit:])): self.create_new_address(for_change) else: break def synchronize(self): with self.lock: self.synchronize_sequence(False) self.synchronize_sequence(True) def is_beyond_limit(self, address): is_change, i = self.get_address_index(address) addr_list = self.get_change_addresses() if is_change else self.get_receiving_addresses() limit = self.gap_limit_for_change if is_change else self.gap_limit if i < limit: return False prev_addresses = addr_list[max(0, i - limit):max(0, i)] for addr in prev_addresses: if self.history.get(addr): return False return True def get_address_index(self, address): return self._addr_to_addr_index[address] def get_master_public_keys(self): return [self.get_master_public_key()] def get_fingerprint(self): return self.get_master_public_key() def get_txin_type(self, address): return self.txin_type class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet): """ Deterministic Wallet with a single pubkey per address """ def __init__(self, storage): Deterministic_Wallet.__init__(self, storage) def get_public_key(self, address): sequence = self.get_address_index(address) pubkey = self.get_pubkey(*sequence) return pubkey def load_keystore(self): self.keystore = load_keystore(self.storage, 'keystore') try: xtype = bip32.xpub_type(self.keystore.xpub) except: xtype = 'standard' self.txin_type = 'p2pkh' if xtype == 'standard' else xtype def get_pubkey(self, c, i): return self.derive_pubkeys(c, i) def add_input_sig_info(self, txin, address): derivation = self.get_address_index(address) x_pubkey = self.keystore.get_xpubkey(*derivation) txin['x_pubkeys'] = [x_pubkey] txin['signatures'] = [None] txin['num_sig'] = 1 def get_master_public_key(self): return self.keystore.get_master_public_key() def derive_pubkeys(self, c, i): return self.keystore.derive_pubkey(c, i) class Standard_Wallet(Simple_Deterministic_Wallet): wallet_type = 'standard' def pubkeys_to_address(self, pubkey): return bitcoin.pubkey_to_address(self.txin_type, pubkey) class Multisig_Wallet(Deterministic_Wallet): # generic m of n gap_limit = 20 def __init__(self, storage): self.wallet_type = storage.get('wallet_type') self.m, self.n = multisig_type(self.wallet_type) Deterministic_Wallet.__init__(self, storage) def get_pubkeys(self, c, i): return self.derive_pubkeys(c, i) def get_public_keys(self, address): sequence = self.get_address_index(address) return self.get_pubkeys(*sequence) def pubkeys_to_address(self, pubkeys): 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) def get_redeem_script(self, address): pubkeys = self.get_public_keys(address) redeem_script = self.pubkeys_to_redeem_script(pubkeys) return redeem_script def derive_pubkeys(self, c, i): return [k.derive_pubkey(c, i) for k in self.get_keystores()] def load_keystore(self): self.keystores = {} for i in range(self.n): name = 'x%d/'%(i+1) self.keystores[name] = load_keystore(self.storage, name) self.keystore = self.keystores['x1/'] xtype = bip32.xpub_type(self.keystore.xpub) self.txin_type = 'p2sh' if xtype == 'standard' else xtype def save_keystore(self): for name, k in self.keystores.items(): self.storage.put(name, k.dump()) def get_keystore(self): return self.keystores.get('x1/') def get_keystores(self): return [self.keystores[i] for i in sorted(self.keystores.keys())] def can_have_keystore_encryption(self): return any([k.may_have_password() for k in self.get_keystores()]) def _update_password_for_keystore(self, old_pw, new_pw): for name, keystore in self.keystores.items(): if keystore.may_have_password(): keystore.update_password(old_pw, new_pw) self.storage.put(name, keystore.dump()) def check_password(self, password): for name, keystore in self.keystores.items(): if keystore.may_have_password(): keystore.check_password(password) self.storage.check_password(password) def get_available_storage_encryption_version(self): # multisig wallets are not offered hw device encryption return STO_EV_USER_PW def has_seed(self): return self.keystore.has_seed() def is_watching_only(self): return all([k.is_watching_only() for k in self.get_keystores()]) def get_master_public_key(self): return self.keystore.get_master_public_key() def get_master_public_keys(self): return [k.get_master_public_key() for k in self.get_keystores()] def get_fingerprint(self): return ''.join(sorted(self.get_master_public_keys())) def add_input_sig_info(self, txin, address): # x_pubkeys are not sorted here because it would be too slow # they are sorted in transaction.get_sorted_pubkeys # pubkeys is set to None to signal that x_pubkeys are unsorted derivation = self.get_address_index(address) x_pubkeys_expected = [k.get_xpubkey(*derivation) for k in self.get_keystores()] x_pubkeys_actual = txin.get('x_pubkeys') # if 'x_pubkeys' is already set correctly (ignoring order, as above), leave it. # otherwise we might delete signatures if x_pubkeys_actual and set(x_pubkeys_actual) == set(x_pubkeys_expected): return txin['x_pubkeys'] = x_pubkeys_expected txin['pubkeys'] = None # we need n place holders txin['signatures'] = [None] * self.n txin['num_sig'] = self.m wallet_types = ['standard', 'multisig', 'imported'] def register_wallet_type(category): wallet_types.append(category) wallet_constructors = { 'standard': Standard_Wallet, 'old': Standard_Wallet, 'xpub': Standard_Wallet, 'imported': Imported_Wallet } def register_constructor(wallet_type, constructor): wallet_constructors[wallet_type] = constructor # former WalletFactory class Wallet(object): """The main wallet "entry point". This class is actually a factory that will return a wallet of the correct type when passed a WalletStorage instance.""" def __new__(self, storage): wallet_type = storage.get('wallet_type') WalletClass = Wallet.wallet_class(wallet_type) wallet = WalletClass(storage) # Convert hardware wallets restored with older versions of # Electrum to BIP44 wallets. A hardware wallet does not have # a seed and plugins do not need to handle having one. rwc = getattr(wallet, 'restore_wallet_class', None) if rwc and storage.get('seed', ''): storage.print_error("converting wallet type to " + rwc.wallet_type) storage.put('wallet_type', rwc.wallet_type) wallet = rwc(storage) return wallet @staticmethod def wallet_class(wallet_type): if multisig_type(wallet_type): return Multisig_Wallet if wallet_type in wallet_constructors: return wallet_constructors[wallet_type] raise WalletFileException("Unknown wallet type: " + str(wallet_type)) ```
Sima160 commented 5 years ago

line 213 reads,

    self.sendaddrcache = 'bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md'
Sima160 commented 5 years ago

i tried reporting this bitcoin address but is not letting me. Bitcoin Address Abuse Screenshot from 2019-05-23 06-11-25

amitie10g commented 5 years ago

Is there a way to sue the scammers? I also lost my funds and I'm angry.

I contacted the FTC and the FBI through the IC3 compliant report (although I'm not from the US). If you're an US citizen, you may also contact a lawyer at least for advisory (free of charge).

I found this WHOIS and provided it to the report, and also reported to Privacy Protect service, in order to find the criminals.

Also, my funds were sent to the following address:

1GK4PjSqYCMVigcuMSY1GmEbhEbijf9Hkr

You may aslo report the address https://electrumstart.net as scam website.

theScrabi commented 5 years ago

Yup I was just cough as well :/ The address I fell with was https://electrumtek.com.

The thing that is bugging me is that I got a notification that my current version has a serious issue and i should download 4.0 when i tried to send a transaction. How did this happen?

OlyaGreen commented 5 years ago

Is there a way to sue the scammers? I also lost my funds and I'm angry.

I contacted the FTC and the FBI through the IC3 compliant report (although I'm not from the US). If you're an US citizen, you may also contact a lawyer at least for advisory (free of charge).

I found this WHOIS and provided it to the report, and also reported to Privacy Protect service, in order to find the criminals.

Also, my funds were sent to the following address:

1GK4PjSqYCMVigcuMSY1GmEbhEbijf9Hkr

You may aslo report the address https://electrumstart.net as scam website.

I was scammed in the EU losing all my savings and will pursue a lawsuit against Electrum as per EU consumer guarantee act.

Anyone else here wants to join forces to file a class action lawsuit? I'm also headed to Electrum Berlin office at Borsigstr. 9. next week, join me if you're in town.

OlyaGreen commented 5 years ago

Yup I was just cough as well :/ The address I fell with was https://electrumtek.com.

The thing that is bugging me is that I got a notification that my current version has a serious issue and i should download 4.0 when i tried to send a transaction. How did this happen?

Where are you located?

OlyaGreen commented 5 years ago

My dear people ! I lost my only bitcoin i had ....just yesterday ! I can not explain how sad i am.

here is my email i sent to Thomas.

"Hi Thomas !

i am sorry for bothering you with this but i beg you please help. i need to write to you. i need your help.

i upgraded/updated my electrum wallet. i think, from 2.6.1 version to the newest Electrum 4.0.0 version. ALSO, i could not find my old Electrum wallet file so i decided to create a new standard wallet file from the seed of my old file. http://docs.electrum.org/en/latest/coldstorage.html#coldstorage i followed on-screen procedure.

THIS WHOLE PROCESS !!! TRANSFFERED MY ONE BITCOIN I HAD !!! TO THIS ADDRESS,

bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md

https://www.blockchain.com/btc/tx/2782105fb86924da86b03cc0e791d6de8843c262ffffaf21833e2999f68a5212

WHAT HAPPENED !!?

my new wallet is empty !!!!!?????

neither my new wallet or old wallet file had any bitcoin addresses that started with bc1. All my addresses start with 1.

i bag you help me what is going on ?

with best regards, S "

Hey Sima!

Did you take any action eventually? I invite you to act together.

OlyaGreen commented 5 years ago

My dear people ! I lost my only bitcoin i had ....just yesterday ! I can not explain how sad i am.

here is my email i sent to Thomas.

"Hi Thomas !

i am sorry for bothering you with this but i beg you please help. i need to write to you. i need your help.

i upgraded/updated my electrum wallet. i think, from 2.6.1 version to the newest Electrum 4.0.0 version. ALSO, i could not find my old Electrum wallet file so i decided to create a new standard wallet file from the seed of my old file. http://docs.electrum.org/en/latest/coldstorage.html#coldstorage i followed on-screen procedure.

THIS WHOLE PROCESS !!! TRANSFFERED MY ONE BITCOIN I HAD !!! TO THIS ADDRESS,

bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md bc1qjmyxwhyjxqjwfyspptvyjhr6kreehxjveec3md

https://www.blockchain.com/btc/tx/2782105fb86924da86b03cc0e791d6de8843c262ffffaf21833e2999f68a5212

WHAT HAPPENED !!?

my new wallet is empty !!!!!?????

neither my new wallet or old wallet file had any bitcoin addresses that started with bc1. All my addresses start with 1.

i bag you help me what is going on ?

with best regards, S "

did Thomas reply to you?

theScrabi commented 5 years ago

Yup I was just cough as well :/ The address I fell with was https://electrumtek.com. The thing that is bugging me is that I got a notification that my current version has a serious issue and i should download 4.0 when i tried to send a transaction. How did this happen?

Where are you located?

@OlyaGreen germany

OlyaGreen commented 5 years ago

Yup I was just cough as well :/ The address I fell with was https://electrumtek.com. The thing that is bugging me is that I got a notification that my current version has a serious issue and i should download 4.0 when i tried to send a transaction. How did this happen?

Where are you located?

@OlyaGreen germany

I'm in Berlin.

Have you filed the police report? We should act together for a class action suit.

OlyaGreen commented 5 years ago

Why is there "CLOSED" above this very important topic, when it is not closed ???

It's Electrum imminent software flaw that caused all of us the loss of life savings. We should act against them together.

Are you located in France? Have you filed a police report?

ecdsa commented 5 years ago

Why is there "CLOSED" above this very important topic, when it is not closed ???

This place is an issue tracker for developers. This issue was closed because the cause of the theft is explained (it was phishing), and because we already fixed the vulnerability in our software (about five months ago). Therefore there is no information here that could be useful for software development.

This does not mean that we are dismissing your issue. You should file a police report and forward that information to us. We are in contact with the german police and we can forward any information you want, regarding your identity and the bitcoin addresses where the stolen funds have been sent.

OlyaGreen commented 5 years ago

Well, I lost 0.7 BTC.

On Mon, 1 Jul 2019 at 12:59, Ernie-FR notifications@github.com wrote:

At least was able to report an abuse at this link https://www.bitcoinabuse.com/reports/1NJ5MK5nkxEkG2nR8oELFVdLaFJwRh7EMC

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spesmilo/electrum/issues/5183?email_source=notifications&email_token=ALH7T3WXTF73PGBEYKNJE53P5HPSRA5CNFSM4G4XOENKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODY5YONI#issuecomment-507217717, or mute the thread https://github.com/notifications/unsubscribe-auth/ALH7T3S6DK3SJ533LHTTUYLP5HPSRANCNFSM4G4XOENA .

--

Olya GreenWriter | Blockchain | Post-IndustrialRead on: https://www.technomads.wtf/ https://www.technomads.wtf/

OlyaGreen commented 5 years ago

Reading my mind here. Faultry products should be recalled from the market, no matter what business model they are - open-source and whatnot. The court is to decide.

My modest $8000 (very hard earned btw and ironically doing content & PR for blockchain startups here in Berlin) is not the biggest amount ever lost in crypto hacks, but I haven't been able to sleep much ever since. Started a fundraiser here - https://www.gofundme.com/recovering-my-savings-from-a-crypto-hack?fbclid=IwAR076Se4B6y5DGZqe9eIXn7GN-WGDTQRmg017tciaoxqqYZefiA4xgEBuQE

On Tue, 2 Jul 2019 at 08:57, Ernie-FR notifications@github.com wrote:

This place is an issue tracker for developers. This issue was closed because the cause of the theft is explained (it was phishing), and because we already fixed the vulnerability in our software (about five months ago). Therefore there is no information here that could be useful for software development.

What I fail to understand is why I was not automaticly and immediatly warned when I opened up my Electrum wallet last sunday after not using it for a year? You developers took on a huge responcebility for handling peoples money, and yet, by the looks of it, it can and will happen to a lot of other users who have not come to update Electrum to the phishing-safe version. What are you saying to them? I think the little message at the opening of the site electrum.org (which is totally confusing cause taking this litteraly means you will never be able to update to any level higher than the current) is a pretty lame way out. I read no regret or excuses about the pain that's being caused. ARE YOU HUMAN AT ALL ? If I would carry that responsability I would redraw the product, alert everyone BIG TIME and start all over again with a new product name, so no confusing situation could exist. Electrum should be pronounced dead so all phishing-sensitive-users out there, can be made aware no matter what version. YOU ARE STILL LETTING IT HAPPEN, which prooves you're not worthy to carry that responsability to handle peoples hard earned money.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spesmilo/electrum/issues/5183?email_source=notifications&email_token=ALH7T3VZ3JLDQAMT53EQHKTP5L363A5CNFSM4G4XOENKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZAIWKY#issuecomment-507546411, or mute the thread https://github.com/notifications/unsubscribe-auth/ALH7T3W2OIFAAL74ODXQPM3P5L363ANCNFSM4G4XOENA .

--

Olya GreenWriter | Blockchain | Post-IndustrialRead on: https://www.technomads.wtf/ https://www.technomads.wtf/

ecdsa commented 5 years ago

What I fail to understand is why I was not automaticly and immediatly warned

That is because software updates are not automated. You were running an old version of the software, that predates the discovery of the phishing attack. (if you think that automated software updates would be better, consider the harm that could be done by a malicious actor taking control of the automated update.)

For me there is no use to go to the police,

Yes, please report the theft to the police, it is useful. We are in contact with the german police, and we asked them to take down the domain names of phishing servers. Since they need to work with authorities of other countries where those domain names are registered, it would help to have official reports from victims. That's why we ask you to report it and forward the info to us.

theScrabi commented 5 years ago

Why is there "CLOSED" above this very important topic, when it is not closed ???

This place is an issue tracker for developers. This issue was closed because the cause of the theft is explained (it was phishing), and because we already fixed the vulnerability in our software (about five months ago). Therefore there is no information here that could be useful for software development.

This does not mean that we are dismissing your issue. You should file a police report and forward that information to us. We are in contact with the german police and we can forward any information you want, regarding your identity and the bitcoin addresses where the stolen funds have been sent.

@ecdsa I went to police and filed a report, should I send it to you?

OlyaGreen commented 5 years ago

Venmo has lots of vulnerable bugs that are likewise not being addressed. Very well put:

'As has recently become painfully clear, if you’re not paying for the product, you are the product'. https://www.wired.com/story/i-scraped-millions-of-venmo-payments-your-data-is-at-risk/

Start a bug bounty program already @spesmilo/electrum reply@reply.github.com

On Tue, 2 Jul 2019 at 11:06, Christian Schabesberger < notifications@github.com> wrote:

Why is there "CLOSED" above this very important topic, when it is not closed ???

This place is an issue tracker for developers. This issue was closed because the cause of the theft is explained (it was phishing), and because we already fixed the vulnerability in our software (about five months ago). Therefore there is no information here that could be useful for software development.

This does not mean that we are dismissing your issue. You should file a police report and forward that information to us. We are in contact with the german police and we can forward any information you want, regarding your identity and the bitcoin addresses where the stolen funds have been sent.

I went to police and got a report, shoul I send it to you?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spesmilo/electrum/issues/5183?email_source=notifications&email_token=ALH7T3WLIFVYS6V44BCZWI3P5MLB7A5CNFSM4G4XOENKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZATHXQ#issuecomment-507589598, or mute the thread https://github.com/notifications/unsubscribe-auth/ALH7T3RBKEUFBWZCDZAF4A3P5MLB7ANCNFSM4G4XOENA .

--

Olya GreenWriter | Blockchain | Post-IndustrialRead on: https://www.technomads.wtf/ https://www.technomads.wtf/

ecdsa commented 5 years ago

@ecdsa I went to police and filed a report, should I send it to you?

yes please use the email address on my profile. thanks!

OlyaGreen commented 5 years ago

anyone residing in Berlin here?

SomberNight commented 5 years ago

@Ernie-FR

If I would carry that responsability I would redraw the product, alert everyone BIG TIME and start all over again with a new product name, so no confusing situation could exist.

alert everyone

we already did that as much as that is realistic at all

start all over again with a new product name, so no confusing situation could exist

The phishers can mimic anything we do. This is open source software. Anyone can change any minor detail and build binaries for themselves. Let's say we changed the name. They too would change the name and just phish users with messages that now use the new name. "Urgent security update!! Download SuperDuperUltraSecure New Wallet now!"

Again, anyone can change any minor detail and build binaries for themselves. The only protection you have is verifying the GPG signature. That is your only way of authenticating the binary.

ldz1 commented 5 years ago

@Ernie-FR

I think the little message at the opening of the site electrum.org (which is totally confusing cause taking this litteraly means you will never be able to update to any level higher than the current) is a pretty lame way out. I read no regret or excuses about the pain that's being caused. ARE YOU HUMAN AT ALL ?

I'm sure >90% of users do not read this warning. It is important that every new user reads this message with understanding at least once. I have proposed the following change on IRC.

electrumorg

OlyaGreen commented 5 years ago

I hope you realize just how much money people are loosing to this problem and just how detrimental that can be to their lives. On Mar 13, 2019, at 9:20 AM, ghost43 @.***> wrote: They allowed a major, known problem that was loosing people massive amounts of money to go on for months and months and months The architecture is decentralised. No one has the power to fix the issue as no one controls the system. Decentralisation has pros and cons. We have spent significant resources on trying to mitigate the issue, see #5084 (comment) but that's all that can be done: mitigations. — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

We're working on gathering all the victims suffered from vulnerable Electrum software to file a class action suit under consumer rights protection. Reach out at olga.i.grinina@gmail.com if you'd like to join!

OlyaGreen commented 5 years ago

@Ernie-FR

I think the little message at the opening of the site electrum.org (which is totally confusing cause taking this litteraly means you will never be able to update to any level higher than the current) is a pretty lame way out. I read no regret or excuses about the pain that's being caused. ARE YOU HUMAN AT ALL ?

I'm sure >90% of users do not read this warning. It is important that every new user reads this message with understanding at least once. I have proposed the following change on IRC.

electrumorg

We're working on gathering all the victims suffered from vulnerable Electrum software to file a class action suit under consumer rights protection. Reach out at olga.i.grinina@gmail.com if you'd like to join!

OlyaGreen commented 5 years ago

Why is there "CLOSED" above this very important topic, when it is not closed ???

This place is an issue tracker for developers. This issue was closed because the cause of the theft is explained (it was phishing), and because we already fixed the vulnerability in our software (about five months ago). Therefore there is no information here that could be useful for software development. This does not mean that we are dismissing your issue. You should file a police report and forward that information to us. We are in contact with the german police and we can forward any information you want, regarding your identity and the bitcoin addresses where the stolen funds have been sent.

@ecdsa I went to police and filed a report, should I send it to you?

We're working on gathering all the victims suffered from vulnerable Electrum software to file a class action suit under consumer rights protection. Reach out at olga.i.grinina@gmail.com if you'd like to join!

OlyaGreen commented 5 years ago

Is there a way to sue the scammers? I also lost my funds and I'm angry.

I contacted the FTC and the FBI through the IC3 compliant report (although I'm not from the US). If you're an US citizen, you may also contact a lawyer at least for advisory (free of charge).

I found this WHOIS and provided it to the report, and also reported to Privacy Protect service, in order to find the criminals.

Also, my funds were sent to the following address:

1GK4PjSqYCMVigcuMSY1GmEbhEbijf9Hkr

You may aslo report the address https://electrumstart.net as scam website.

We're working on gathering all the victims suffered from vulnerable Electrum software to file a class action suit under consumer rights protection. Reach out at olga.i.grinina@gmail.com if you'd like to join!

OlyaGreen commented 5 years ago

@Ernie-FR

I think the little message at the opening of the site electrum.org (which is totally confusing cause taking this litteraly means you will never be able to update to any level higher than the current) is a pretty lame way out. I read no regret or excuses about the pain that's being caused. ARE YOU HUMAN AT ALL ?

I'm sure >90% of users do not read this warning. It is important that every new user reads this message with understanding at least once. I have proposed the following change on IRC.

electrumorg

NO ONE follows Electrum website, or reads the website. Seriously, how many of you did??

Got a tip for Electrum 'team': try posting on social media at least occasionally, or rather EVERY DAY, to give your 'precious' users a chance to notice this fuck up.

I lost 10k, and I'm getting it back.

ldz1 commented 5 years ago

@OlyaGreen: Maybe bitcoin is not for you. There is a classic banking, which takes responsibility for your money. Bitcoin and its community does not take that responsibility. If you do not inform yourself about some things, then you may lose money. I'm so sorry.

OlyaGreen commented 5 years ago

oh wow, another wise man here.

I know what bitcoin is, check my profile for starters.

re responsibility - as long as there are monetary losses incurred by users of vulnerable software and regulations protecting consumers, it's for the court to decide. Not you, not anyone else in the 'bitcoin community'. Educate yourself, that helps.

On Sat, 6 Jul 2019 at 19:13, ldz1 notifications@github.com wrote:

@OlyaGreen https://github.com/OlyaGreen: Maybe bitcoin is not for you. There is a classic banking, which takes responsibility for your money. Bitcoin and its community does not take that responsibility. If you do not inform yourself about some things, then you may lose money. I'm so sorry.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spesmilo/electrum/issues/5183?email_source=notifications&email_token=ALH7T3QL5JIVL3GXH5BG5FTP6DHFBA5CNFSM4G4XOENKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZK5GBQ#issuecomment-508941062, or mute the thread https://github.com/notifications/unsubscribe-auth/ALH7T3SU6X4GT46WIILZRDLP6DHFBANCNFSM4G4XOENA .

--

-- Olya GreenContent & Communications | Emerging tech & Blockchain Read on: https://www.technomads.wtf/ https://www.technomads.wtf/

flin1314 commented 5 years ago

I had electrum 3.0.6 and try to send bitcoin and there was no response. I had to restart my wallet a few times. Finally, I was able to but after I clicked send, a message pop up says Required Security Update (V4.0.0). I trusted the links because it comes from electrum wallet (3.0.6).  I wasn't any other website to download anything.

But after I upgrade to 4.0 all my bitcoin was gone. :o

Electrum wallet is a con

flin1314 commented 5 years ago

@Ernie-FR

I think the little message at the opening of the site electrum.org (which is totally confusing cause taking this litteraly means you will never be able to update to any level higher than the current) is a pretty lame way out. I read no regret or excuses about the pain that's being caused. ARE YOU HUMAN AT ALL ?

I'm sure >90% of users do not read this warning. It is important that every new user reads this message with understanding at least once. I have proposed the following change on IRC. electrumorg

NO ONE follows Electrum website, or reads the website. Seriously, how many of you did??

Got a tip for Electrum 'team': try posting on social media at least occasionally, or rather EVERY DAY, to give your 'precious' users a chance to notice this fuck up.

I lost 10k, and I'm getting it back.

Same here I have just lost 1.22 bitcoin. That was all my saving. Most like Electrum create this bug and stole our bitcoins