diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 7f8534ceb..fa5bfa565 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -1,3 +1,8 @@
+# Release 2.4.2
+ * Command line can read arguments from stdin (pipe)
+ * Speedup fee computation for large transactions
+ * Various bugfixes
+
# Release 2.4.1
* Use ssl.PROTOCOL_TLSv1
* Fix DNSSEC issues with ECDSA signatures
diff --git a/contrib/make_android b/contrib/make_android
index 11fa70f10..cfc796fba 100755
--- a/contrib/make_android
+++ b/contrib/make_android
@@ -21,8 +21,6 @@ if __name__ == '__main__':
shutil.copyfile('scripts/authenticator.py', target + '/authenticator.py')
shutil.copytree("packages",'dist/e4a-%s/packages'%version, ignore=shutil.ignore_patterns('*.pyc'))
shutil.copytree("lib",'dist/e4a-%s/lib'%version, ignore=shutil.ignore_patterns('*.pyc'))
- # dns is not used by android app
- os.system('rm -rf %s/packages/dns'%target)
os.mkdir(target + '/gui')
shutil.copyfile('gui/android.py', target + '/gui/android.py')
open(target + '/gui/__init__.py','w').close()
diff --git a/docs/android.html b/docs/android.html
deleted file mode 100644
index 1aed1a47d..000000000
--- a/docs/android.html
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
Electrum for Android
-
-This page explains how to install Electrum on Android devices.
-
-Please note that Electrum is not distributed as a binary package, but
-as python source code; this gives users the possibility to see what
-the code is doing, and to check that it does not contain malware. The
-downside is that installation is slightly more complicated than
-downloading an app on the Android market, but it remains very
-simple.
-
-It is possible to print this page on paper and to install everything from
-QR codes. If you encounter problems, you may find help at
-
-this link.
-
-
-
1. Download and install Google Scripting Layer for Android
-
-You can get
-it
here,
-or by scanning the following qr code:
-

-
-
-
2. Download and install Python for Android
-
-You can get
-it
here,
-or by scanning the following qr code:
-

-
-Once you have installed the apk, launch the Python for Android application and click 'install'
-
-
3. Download the Electrum install script
-
-Download
e4a_install.py and install it in your sl4a/scripts directory.
-You can do it manually, or from QR code, as follows:
-
-1. Launch SL4A.
-2. Press the Menu button.
-3. Tap Add.
-4. Tap Scan Barcode.
-5. Scan the following QRcode:
-
-
%0Azipfile.ZipFile(nz).extractall()%0Aos.rename(n%2C'scripts%2F'%2Bn))
-
This will install a script named e4a_install.py
-
-
4. Download and install Electrum
-
-1. Tap e4a_install.py: it will download and install a directory named "Electrum-0.43d"
-2. To launch Electrum, visit the "Electrum-0.43d" directory and tap 'electrum4a.py'
-
-
-
diff --git a/docs/cold_storage b/docs/cold_storage
deleted file mode 100644
index f94a524e7..000000000
--- a/docs/cold_storage
+++ /dev/null
@@ -1,31 +0,0 @@
-Here is how to sign a transaction with an offline Electrum wallet.
-
-1. With your online (seedless) wallet, create the transaction using 'mktx':
-
-./electrum -w seedless_wallet mktx 1Cpf9zb5Rm5Z5qmmGezn6ERxFWvwuZ6UCx 0.1
-{
- "complete": false,
- "hex": "01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000004801ff45fe197f1a7a7779f58690c3100364d7ce596bf47bb52e88e617e22940bf54a8f139194652584b0d357eb95defb8b4911b0a53118b8afecb96aedb1334e772df350901002800ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000"
-}
-
-Electrum returns an unsigned transaction. Note that the serialization
-format contains the master public key needed and key derivation, used
-by the offline wallet to sign the transaction.
-
-
-2. Sign the transaction with your offline wallet, using 'signrawtransaction':
-
-./electrum -w wallet_with_seed signrawtransaction 01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000004801ff45fe197f1a7a7779f58690c3100364d7ce596bf47bb52e88e617e22940bf54a8f139194652584b0d357eb95defb8b4911b0a53118b8afecb96aedb1334e772df350901002800ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000
-Password:
-{
- "complete": true,
- "hex": "01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000008b483045022100c65dd8899d4e1d12b1ebaa0ea15835f9a158343733fbe990cdfebde2164d89c802201a5a8fe737b07daf700aeecf3b6a4111c563ebc181da75b1f264883060c273da0141040beb415f075a532982fe982d01736453d4e3413566c79a39d16679474c7ab94022269b9f726edc152a89dfcf18cd3dd2a38fc5e442f24d22a51545ca42beb7b5ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000"
-}
-
-The result is a fully signed transaction, as indicated by the "complete" field.
-
-
-3. Broadcast the transaction to the Bitcoin network, using 'sendrawtransaction':
-
- ./electrum sendrawtransaction 01000000015a92850cc5dc7bb7c1a711c1ce0c1658596c085d49c17fce68c641cce0bdd188010000008b483045022100c65dd8899d4e1d12b1ebaa0ea15835f9a158343733fbe990cdfebde2164d89c802201a5a8fe737b07daf700aeecf3b6a4111c563ebc181da75b1f264883060c273da0141040beb415f075a532982fe982d01736453d4e3413566c79a39d16679474c7ab94022269b9f726edc152a89dfcf18cd3dd2a38fc5e442f24d22a51545ca42beb7b5ffffffff02b1f0f65d000000001976a9147ea19cc36d846e2ce81762def3cb9299bed0847188ac80969800000000001976a91451e814c0f7637ba9a59bc11628337a2df6559a5088ac00000000
-"ef6b561232f3c507219ab7d2a79f8849e14ed7e926e77546c2d9e751905b825b"
diff --git a/docs/console.html b/docs/console.html
deleted file mode 100644
index e8430f023..000000000
--- a/docs/console.html
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-This is the documentation for the Electrum Console.
-
-
-
-
-Most Electrum command-line commands are also available in the console.
-The results are Python objects, even though they are
-sometimes rendered as JSON for clarity.
-
-Let us call
listunspent(), to see the list of unspent outputs in the wallet:
-
->> listunspent()
-[
- {
- "address": "12cmY5RHRgx8KkUKASDcDYRotget9FNso3",
- "index": 0,
- "raw_output_script": "76a91411bbdc6e3a27c44644d83f783ca7df3bdc2778e688ac",
- "tx_hash": "e7029df9ac8735b04e8e957d0ce73987b5c9c5e920ec4a445130cdeca654f096",
- "value": 0.01
- },
- {
- "address": "1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF",
- "index": 0,
- "raw_output_script": "76a914aaf437e25805f288141bfcdc27887ee5492bd13188ac",
- "tx_hash": "b30edf57ca2a31560b5b6e8dfe567734eb9f7d3259bb334653276efe520735df",
- "value": 9.04735316
- }
-]
-
-Note that the result is rendered as JSON.
-However, if we save it to a Python variable, it is rendered as a Python object:
-
->> u = listunspent()
->> u
-[{'tx_hash': u'e7029df9ac8735b04e8e957d0ce73987b5c9c5e920ec4a445130cdeca654f096', 'index': 0, 'raw_output_script': '76a91411bbdc6e3a27c44644d83f783ca7df3bdc2778e688ac', 'value': 0.01, 'address': '12cmY5RHRgx8KkUKASDcDYRotget9FNso3'}, {'tx_hash': u'b30edf57ca2a31560b5b6e8dfe567734eb9f7d3259bb334653276efe520735df', 'index': 0, 'raw_output_script': '76a914aaf437e25805f288141bfcdc27887ee5492bd13188ac', 'value': 9.04735316, 'address': '1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF'}]
-
-
-This makes it possible to combine Electrum commands with Python.
-For example, let us pick only the addresses in the previous result:
-
->> map(lambda x:x.get('address'), listunspent())
-[
- "12cmY5RHRgx8KkUKASDcDYRotget9FNso3",
- "1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF"
-]
-
-Here we combine two commands,
listunspent
-and
dumpprivkeys, in order to dump the private keys of all adresses that have unspent outputs:
-
->> dumpprivkeys( map(lambda x:x.get('address'), listunspent()) )
-{
- "12cmY5RHRgx8KkUKASDcDYRotget9FNso3": "***************************************************",
- "1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF": "***************************************************"
-}
-
-Note that
dumpprivkey will ask for your password if your
-wallet is encrypted.
-
-The GUI methods can be accessed through the
gui variable.
-For example, you can display a QR code from a string using
-gui.show_qrcode.
-Example:
-
-gui.show_qrcode(dumpprivkey(listunspent()[0]['address']))
-
-
-
-
-
diff --git a/electrum b/electrum
index 0850c47ce..e0f5dd6f0 100755
--- a/electrum
+++ b/electrum
@@ -80,14 +80,11 @@ from electrum.commands import get_parser, known_commands, Commands, config_varia
# get password routine
def prompt_password(prompt, confirm=True):
import getpass
- if sys.stdin.isatty():
- password = getpass.getpass(prompt)
- if password and confirm:
- password2 = getpass.getpass("Confirm: ")
- if password != password2:
- sys.exit("Error: Passwords do not match.")
- else:
- password = raw_input(prompt)
+ password = getpass.getpass(prompt)
+ if password and confirm:
+ password2 = getpass.getpass("Confirm: ")
+ if password != password2:
+ sys.exit("Error: Passwords do not match.")
if not password:
password = None
return password
@@ -272,14 +269,16 @@ def run_cmdline(config):
always_hook('cmdline_load_wallet', wallet)
# important warning
- if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
+ if cmd.name in ['getprivatekeys']:
print_stderr("WARNING: ALL your private keys are secret.")
print_stderr("Exposing a single private key can compromise your entire wallet!")
print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
# commands needing password
- if cmd.requires_password:
- if wallet.use_encryption:
+ if cmd.requires_password and wallet.use_encryption:
+ if config.get('password'):
+ password = config.get('password')
+ else:
password = prompt_password('Password:', False)
if not password:
print_msg("Error: Password required")
@@ -290,8 +289,6 @@ def run_cmdline(config):
except InvalidPassword:
print_msg("Error: This password does not decode this wallet.")
sys.exit(1)
- else:
- password = None
else:
password = None
@@ -372,6 +369,23 @@ if __name__ == '__main__':
sys.argv.remove('help')
sys.argv.append('-h')
+ # read arguments from stdin pipe and prompt
+ for i, arg in enumerate(sys.argv):
+ if arg == '-':
+ if not sys.stdin.isatty():
+ pipe_data = sys.stdin.read()
+ try:
+ pipe_data = json.loads(pipe_data)
+ except:
+ pass
+ sys.argv[i] = pipe_data
+ break
+ else:
+ raise BaseException('Cannot get argument from stdin')
+ elif arg == '?':
+ sys.argv[i] = prompt_password('Enter argument (will not echo):', False)
+
+ # parse cmd line
parser = get_parser(run_gui, run_daemon, run_cmdline)
args = parser.parse_args()
@@ -393,10 +407,6 @@ if __name__ == '__main__':
if config_options.get('portable'):
config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
- # If private key is passed on the command line, '?' triggers prompt.
- if config_options.get('privkey') and config_options['privkey'] == '?':
- config_options['privkey'] = prompt_password('Enter PrivateKey (will not echo):', False)
-
set_verbosity(config_options.get('verbose'))
config = SimpleConfig(config_options)
diff --git a/gui/android.py b/gui/android.py
index 875a98723..537adf289 100644
--- a/gui/android.py
+++ b/gui/android.py
@@ -607,10 +607,13 @@ def payto_loop():
if data:
print "data", data
if re.match('^bitcoin:', data):
- payto, amount, label, message, _ = util.parse_URI(data)
+ rr = util.parse_URI(data)
+ amount = rr.get('amount')
+ address = rr.get('address')
+ message = rr.get('message', '')
if amount:
- amount = str(amount / COIN)
- droid.fullSetProperty("recipient", "text", payto)
+ amount = str(Decimal(amount)/COIN)
+ droid.fullSetProperty("recipient", "text", address)
droid.fullSetProperty("amount", "text", amount)
droid.fullSetProperty("message", "text", message)
elif is_address(data):
@@ -771,7 +774,7 @@ def settings_loop():
def set_listview():
host, port, p, proxy_config, auto_connect = network.get_parameters()
- fee = str(Decimal(wallet.fee_per_kb) / COIN)
+ fee = str(Decimal(wallet.fee_per_kb(config)) / COIN)
is_encrypted = 'yes' if wallet.use_encryption else 'no'
protocol = protocol_name(p)
droid.fullShow(settings_layout)
@@ -818,8 +821,10 @@ def settings_loop():
network_changed = True
elif pos == "3": #fee
- fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ',
- str(Decimal(wallet.fee_per_kb) / COIN), "numberDecimal")
+ fee = modal_input(
+ 'Transaction fee',
+ 'The fee will be this amount multiplied by the number of inputs in your transaction. ',
+ str(Decimal(wallet.fee_per_kb(config)) / COIN), "numberDecimal")
if fee:
try:
fee = int(COIN * Decimal(fee))
@@ -900,7 +905,7 @@ config = None
class ElectrumGui:
def __init__(self, _config, _network):
- global wallet, network, contacts
+ global wallet, network, contacts, config
network = _network
config = _config
network.register_callback('updated', update_callback)
diff --git a/gui/qt/history_widget.py b/gui/qt/history_widget.py
index 94acd43ea..847c728a4 100644
--- a/gui/qt/history_widget.py
+++ b/gui/qt/history_widget.py
@@ -28,10 +28,27 @@ from electrum.plugins import run_hook
class HistoryWidget(MyTreeWidget):
def __init__(self, parent=None):
- MyTreeWidget.__init__(self, parent, self.create_menu, [ '', _('Date'), _('Description') , _('Amount'), _('Balance')], 2)
+ MyTreeWidget.__init__(self, parent, self.create_menu, ['', '', _('Date'), _('Description') , _('Amount'), _('Balance')], 3)
+ self.setColumnHidden(1, True)
self.config = self.parent.config
self.setSortingEnabled(False)
+ def get_icon(self, conf, timestamp):
+ time_str = _("unknown")
+ if conf > 0:
+ time_str = format_time(timestamp)
+ if conf == -1:
+ time_str = 'unverified'
+ icon = QIcon(":icons/unconfirmed.png")
+ elif conf == 0:
+ time_str = 'pending'
+ icon = QIcon(":icons/unconfirmed.png")
+ elif conf < 6:
+ icon = QIcon(":icons/clock%d.png"%conf)
+ else:
+ icon = QIcon(":icons/confirmed.png")
+ return icon, time_str
+
def update(self, h):
self.wallet = self.parent.wallet
item = self.currentItem()
@@ -39,41 +56,35 @@ class HistoryWidget(MyTreeWidget):
self.clear()
for item in h:
tx_hash, conf, value, timestamp, balance = item
- time_str = _("unknown")
if conf is None and timestamp is None:
continue # skip history in offline mode
- if conf > 0:
- time_str = format_time(timestamp)
- if conf == -1:
- time_str = 'unverified'
- icon = QIcon(":icons/unconfirmed.png")
- elif conf == 0:
- time_str = 'pending'
- icon = QIcon(":icons/unconfirmed.png")
- elif conf < 6:
- icon = QIcon(":icons/clock%d.png"%conf)
- else:
- icon = QIcon(":icons/confirmed.png")
+ icon, time_str = self.get_icon(conf, timestamp)
v_str = self.parent.format_amount(value, True, whitespaces=True)
balance_str = self.parent.format_amount(balance, whitespaces=True)
label, is_default_label = self.wallet.get_label(tx_hash)
- item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
- item.setFont(2, QFont(MONOSPACE_FONT))
+ item = QTreeWidgetItem(['', tx_hash, time_str, label, v_str, balance_str])
+ item.setIcon(0, icon)
item.setFont(3, QFont(MONOSPACE_FONT))
item.setFont(4, QFont(MONOSPACE_FONT))
+ item.setFont(5, QFont(MONOSPACE_FONT))
if value < 0:
- item.setForeground(3, QBrush(QColor("#BC1E1E")))
+ item.setForeground(4, QBrush(QColor("#BC1E1E")))
if tx_hash:
item.setData(0, Qt.UserRole, tx_hash)
if is_default_label:
- item.setForeground(2, QBrush(QColor('grey')))
- item.setIcon(0, icon)
+ item.setForeground(3, QBrush(QColor('grey')))
self.insertTopLevelItem(0, item)
if current_tx == tx_hash:
self.setCurrentItem(item)
-
run_hook('history_tab_update')
+ def update_item(self, tx_hash, conf, timestamp):
+ icon, time_str = self.get_icon(conf, timestamp)
+ items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)
+ if items:
+ item = items[0]
+ item.setIcon(0, icon)
+ item.setText(2, time_str)
def create_menu(self, position):
self.selectedIndexes()
diff --git a/gui/qt/lite_window.py b/gui/qt/lite_window.py
index 153049dd0..300098645 100644
--- a/gui/qt/lite_window.py
+++ b/gui/qt/lite_window.py
@@ -792,9 +792,9 @@ class MiniDriver(QObject):
self.network = main_window.network
self.window = mini_window
- if self.network:
- self.network.register_callback('updated',self.update_callback)
- self.network.register_callback('status', self.update_callback)
+ #if self.network:
+ # self.network.register_callback('updated',self.update_callback)
+ # self.network.register_callback('status', self.update_callback)
self.state = None
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
index df85a19d5..ae3e7c9cb 100644
--- a/gui/qt/main_window.py
+++ b/gui/qt/main_window.py
@@ -163,23 +163,19 @@ class ElectrumWindow(QMainWindow):
for i in range(tabs.count()):
QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
- self.connect(self, QtCore.SIGNAL('stop'), self.close)
- self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
- self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
- self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
self.labelsChanged.connect(self.update_tabs)
-
self.history_list.setFocus(True)
# network callbacks
if self.network:
self.network.register_callback('updated', lambda: self.need_update.set())
- self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
- self.network.register_callback('status', lambda: self.emit(QtCore.SIGNAL('update_status')))
- self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
- self.network.register_callback('stop', lambda: self.emit(QtCore.SIGNAL('stop')))
+ self.network.register_callback('new_transaction', self.new_transaction)
+ self.register_callback('status', self.update_status)
+ self.register_callback('close', self.close)
+ self.register_callback('banner', self.console.showMessage)
+ self.register_callback('verified', self.history_list.update_item)
# set initial message
self.console.showMessage(self.network.banner)
@@ -189,6 +185,15 @@ class ElectrumWindow(QMainWindow):
self.not_enough_funds = False
self.pluginsdialog = None
self.fetch_alias()
+ self.require_fee_update = False
+ self.tx_notifications = []
+
+
+ def register_callback(self, name, method):
+ """ run callback in the qt thread """
+ self.connect(self, QtCore.SIGNAL(name), method)
+ self.network.register_callback(name, lambda *params: self.emit(QtCore.SIGNAL(name), *params))
+
def fetch_alias(self):
self.alias_info = None
@@ -386,7 +391,7 @@ class ElectrumWindow(QMainWindow):
b = os.path.basename(k)
def loader(k):
return lambda: self.load_wallet_file(k)
- self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence("Ctrl+%d"%i))
+ self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence("Ctrl+%d"%(i+1)))
self.recently_visited_menu.setEnabled(len(recent))
def init_menubar(self):
@@ -396,6 +401,7 @@ class ElectrumWindow(QMainWindow):
self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
+ file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
file_menu.addSeparator()
file_menu.addAction(_("&Quit"), self.close)
self.update_recently_visited()
@@ -460,30 +466,30 @@ class ElectrumWindow(QMainWindow):
_("Please report any bugs as issues on github:")+"
https://github.com/spesmilo/electrum/issues")
+ def new_transaction(self, tx):
+ self.tx_notifications.append(tx)
+
def notify_transactions(self):
if not self.network or not self.network.is_connected():
return
-
print_error("Notifying GUI")
- if len(self.network.pending_transactions_for_notifications) > 0:
+ if len(self.tx_notifications) > 0:
# Combine the transactions if there are more then three
- tx_amount = len(self.network.pending_transactions_for_notifications)
+ tx_amount = len(self.tx_notifications)
if(tx_amount >= 3):
total_amount = 0
- for tx in self.network.pending_transactions_for_notifications:
- is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
+ for tx in self.tx_notifications:
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
if(v > 0):
total_amount += v
-
self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
- % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
-
- self.network.pending_transactions_for_notifications = []
+ % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
+ self.tx_notifications = []
else:
- for tx in self.network.pending_transactions_for_notifications:
+ for tx in self.tx_notifications:
if tx:
- self.network.pending_transactions_for_notifications.remove(tx)
- is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
+ self.tx_notifications.remove(tx)
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
if(v > 0):
self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
@@ -524,6 +530,10 @@ class ElectrumWindow(QMainWindow):
self.need_update.clear()
# resolve aliases
self.payto_e.resolve()
+ # update fee
+ if self.require_fee_update:
+ self.do_update_fee()
+ self.require_fee_update = False
run_hook('timer_actions')
def format_amount(self, x, is_diff=False, whitespaces=False):
@@ -1001,24 +1011,26 @@ class ElectrumWindow(QMainWindow):
def on_shortcut():
sendable = self.get_sendable_balance()
inputs = self.get_coins()
- for i in inputs: self.wallet.add_input_info(i)
+ for i in inputs:
+ self.wallet.add_input_info(i)
addr = self.payto_e.payto_address if self.payto_e.payto_address else self.dummy_address
output = ('address', addr, sendable)
dummy_tx = Transaction.from_io(inputs, [output])
- if not self.fee_e.isModified():
+ if self.fee_e.get_amount() is None:
fee_per_kb = self.wallet.fee_per_kb(self.config)
self.fee_e.setAmount(self.wallet.estimated_fee(dummy_tx, fee_per_kb))
self.amount_e.setAmount(max(0, sendable - self.fee_e.get_amount()))
+ # emit signal for fiat_amount update
self.amount_e.textEdited.emit("")
self.amount_e.shortcut.connect(on_shortcut)
- self.payto_e.textChanged.connect(lambda: self.update_fee())
- self.amount_e.textEdited.connect(lambda: self.update_fee())
- self.fee_e.textEdited.connect(lambda: self.update_fee())
+ self.payto_e.textChanged.connect(self.update_fee)
+ self.amount_e.textEdited.connect(self.update_fee)
+ self.fee_e.textEdited.connect(self.update_fee)
# This is so that when the user blanks the fee and moves on,
# we go back to auto-calculate mode and put a fee back.
- self.fee_e.editingFinished.connect(lambda: self.update_fee())
+ self.fee_e.editingFinished.connect(self.update_fee)
def entry_changed():
text = ""
@@ -1070,6 +1082,9 @@ class ElectrumWindow(QMainWindow):
return w
def update_fee(self):
+ self.require_fee_update = True
+
+ def do_update_fee(self):
'''Recalculate the fee. If the fee was manually input, retain it, but
still build the TX to see if there are enough funds.
'''
@@ -1224,9 +1239,10 @@ class ElectrumWindow(QMainWindow):
outputs, fee, tx_desc, coins = r
try:
tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
- if not tx:
- raise BaseException(_("Insufficient funds"))
- except Exception as e:
+ except NotEnoughFunds:
+ self.show_message(_("Insufficient funds"))
+ return
+ except BaseException as e:
traceback.print_exc(file=sys.stdout)
self.show_message(str(e))
return
@@ -1690,9 +1706,9 @@ class ElectrumWindow(QMainWindow):
name = self.wallet.get_account_name(k)
c, u, x = self.wallet.get_account_balance(k)
account_item = QTreeWidgetItem([ name, '', self.format_amount(c + u + x), ''])
- l.addTopLevelItem(account_item)
account_item.setExpanded(self.accounts_expanded.get(k, True))
account_item.setData(0, Qt.UserRole, k)
+ l.addTopLevelItem(account_item)
else:
account_item = l
sequences = [0,1] if account.has_change() else [0]
@@ -1848,7 +1864,7 @@ class ElectrumWindow(QMainWindow):
def do_search(self, t):
i = self.tabs.currentIndex()
if i == 0:
- self.history_list.filter(t, [1, 2, 3]) # Date, Description, Amount
+ self.history_list.filter(t, [2, 3, 4]) # Date, Description, Amount
elif i == 1:
self.invoices_list.filter(t, [0, 1, 2, 3]) # Date, Requestor, Description, Amount
elif i == 2:
@@ -2245,7 +2261,7 @@ class ElectrumWindow(QMainWindow):
tx = self.tx_from_text(data)
if not tx:
return
- self.show_transaction(tx, prompt_if_unsaved=True)
+ self.show_transaction(tx)
def read_tx_from_file(self):
@@ -2267,7 +2283,7 @@ class ElectrumWindow(QMainWindow):
return
tx = self.tx_from_text(text)
if tx:
- self.show_transaction(tx, prompt_if_unsaved=True)
+ self.show_transaction(tx)
def do_process_from_file(self):
tx = self.read_tx_from_file()
@@ -2597,15 +2613,16 @@ class ElectrumWindow(QMainWindow):
gui_widgets.append((nz_label, nz))
msg = _('Fee per kilobyte of transaction.') + '\n' \
- + _('If you enable dynamic fees, and this parameter will be used as upper bound.')
+ + _('If you enable dynamic fees, this parameter will be used as upper bound.')
fee_label = HelpLabel(_('Transaction fee per kb') + ':', msg)
fee_e = BTCkBEdit(self.get_decimal_point)
fee_e.setAmount(self.config.get('fee_per_kb', bitcoin.RECOMMENDED_FEE))
def on_fee(is_done):
+ if self.config.get('dynamic_fees'):
+ return
v = fee_e.get_amount() or 0
self.config.set_key('fee_per_kb', v, is_done)
- if not is_done:
- self.update_fee()
+ self.update_fee()
fee_e.editingFinished.connect(lambda: on_fee(True))
fee_e.textEdited.connect(lambda: on_fee(False))
tx_widgets.append((fee_label, fee_e))
@@ -2915,16 +2932,3 @@ class ElectrumWindow(QMainWindow):
text.setText(mpk_text)
vbox.addLayout(Buttons(CloseButton(d)))
d.exec_()
-
- @protected
- def create_csr(self, alias, challenge, password):
- from electrum import x509
- import tlslite
- xprv = self.wallet.get_master_private_key(self.wallet.root_name, password)
- _, _, _, c, k = bitcoin.deserialize_xkey(xprv)
- csr = x509.create_csr(alias, challenge, k)
- csr = tlslite.utils.pem.pem(bytearray(csr), "CERTIFICATE REQUEST")
- with open('test.csr', 'w') as f:
- f.write(csr)
- #os.system('openssl asn1parse -i -in test.csr')
- return 'test.csr'
diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py
index 1316f543a..c1da6e0c1 100644
--- a/gui/qt/paytoedit.py
+++ b/gui/qt/paytoedit.py
@@ -75,39 +75,34 @@ class PayToEdit(ScanQRTextEdit):
x, y = line.split(',')
n = re.match('^SCRIPT\s+([0-9a-fA-F]+)$', x.strip())
if n:
- _type = 'script'
- address = n.group(1).decode('hex')
+ script = str(n.group(1)).decode('hex')
amount = self.parse_amount(y)
+ return 'script', script, amount
else:
- _type = 'address'
address = self.parse_address(x)
amount = self.parse_amount(y)
- return _type, address, amount
-
+ return 'address', address, amount
def parse_amount(self, x):
p = pow(10, self.amount_edit.decimal_point())
- return int( p * Decimal(x.strip()))
-
+ return int(p * Decimal(x.strip()))
def parse_address(self, line):
r = line.strip()
m = re.match('^'+RE_ALIAS+'$', r)
- address = m.group(2) if m else r
+ address = str(m.group(2) if m else r)
assert bitcoin.is_address(address)
return address
-
def check_text(self):
self.errors = []
if self.is_pr:
return
# filter out empty lines
- lines = filter( lambda x: x, self.lines())
+ lines = filter(lambda x: x, self.lines())
outputs = []
total = 0
self.payto_address = None
-
if len(lines) == 1:
data = lines[0]
if data.startswith("bitcoin:"):
@@ -123,12 +118,12 @@ class PayToEdit(ScanQRTextEdit):
for i, line in enumerate(lines):
try:
- type, to_address, amount = self.parse_address_and_amount(line)
+ _type, to_address, amount = self.parse_address_and_amount(line)
except:
self.errors.append((i, line.strip()))
continue
- outputs.append((type, to_address, amount))
+ outputs.append((_type, to_address, amount))
total += amount
self.outputs = outputs
@@ -158,10 +153,8 @@ class PayToEdit(ScanQRTextEdit):
return self.outputs[:]
-
def lines(self):
- return str(self.toPlainText()).split('\n')
-
+ return unicode(self.toPlainText()).split('\n')
def is_multiline(self):
return len(self.lines()) > 1
diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py
index 525ca7919..a69ecfac6 100644
--- a/gui/qt/transaction_dialog.py
+++ b/gui/qt/transaction_dialog.py
@@ -234,7 +234,7 @@ class TxDialog(QDialog):
if self.tx.locktime > 0:
vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))
- vbox.addWidget(QLabel(_("Inputs")))
+ vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs)))
ext = QTextCharFormat()
rec = QTextCharFormat()
@@ -273,7 +273,7 @@ class TxDialog(QDialog):
cursor.insertBlock()
vbox.addWidget(i_text)
- vbox.addWidget(QLabel(_("Outputs")))
+ vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs)))
o_text = QTextEdit()
o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True)
diff --git a/gui/qt/util.py b/gui/qt/util.py
index cb78c41d5..bd2e8863e 100644
--- a/gui/qt/util.py
+++ b/gui/qt/util.py
@@ -335,6 +335,8 @@ class MyTreeWidget(QTreeWidget):
self.is_edit = False
def label_changed(self, item, column):
+ if column != self.edit_column:
+ return
if self.is_edit:
return
self.is_edit = True
diff --git a/lib/account.py b/lib/account.py
index a50d3f037..bae7fc129 100644
--- a/lib/account.py
+++ b/lib/account.py
@@ -187,7 +187,6 @@ class OldAccount(Account):
@classmethod
def mpk_from_seed(klass, seed):
- curve = SECP256k1
secexp = klass.stretch_key(seed)
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
@@ -211,11 +210,10 @@ class OldAccount(Account):
@classmethod
def get_pubkey_from_mpk(self, mpk, for_change, n):
- curve = SECP256k1
z = self.get_sequence(mpk, for_change, n)
- master_public_key = ecdsa.VerifyingKey.from_string( mpk, curve = SECP256k1 )
- pubkey_point = master_public_key.pubkey.point + z*curve.generator
- public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
+ master_public_key = ecdsa.VerifyingKey.from_string(mpk, curve = SECP256k1)
+ pubkey_point = master_public_key.pubkey.point + z*SECP256k1.generator
+ public_key2 = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve = SECP256k1)
return '04' + public_key2.to_string().encode('hex')
def derive_pubkeys(self, for_change, n):
@@ -239,7 +237,6 @@ class OldAccount(Account):
def check_seed(self, seed):
- curve = SECP256k1
secexp = self.stretch_key(seed)
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
master_public_key = master_private_key.get_verifying_key().to_string()
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
index 96b61b429..1cbbf4629 100644
--- a/lib/bitcoin.py
+++ b/lib/bitcoin.py
@@ -201,6 +201,7 @@ def hash_160(public_key):
md.update(sha256(public_key))
return md.digest()
except Exception:
+ # not available in Android SL4a
import ripemd
md = ripemd.new(sha256(public_key))
return md.digest()
@@ -604,8 +605,6 @@ def CKD_priv(k, c, n):
return _CKD_priv(k, c, rev_hex(int_to_hex(n,4)).decode('hex'), is_prime)
def _CKD_priv(k, c, s, is_prime):
- import hmac
- from ecdsa.util import string_to_number, number_to_string
order = generator_secp256k1.order()
keypair = EC_KEY(k)
cK = GetPubKey(keypair.pubkey,True)
@@ -627,8 +626,6 @@ def CKD_pub(cK, c, n):
# helper function, callable with arbitrary string
def _CKD_pub(cK, c, s):
- import hmac
- from ecdsa.util import string_to_number, number_to_string
order = generator_secp256k1.order()
I = hmac.new(c, cK + s, hashlib.sha512).digest()
curve = SECP256k1
@@ -706,7 +703,6 @@ def xpub_from_xprv(xprv, testnet=False):
def bip32_root(seed, testnet=False):
- import hmac
header_pub, header_priv = _get_headers(testnet)
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
master_k = I[0:32]
diff --git a/lib/commands.py b/lib/commands.py
index 4ae6e4766..5e793ae63 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -155,7 +155,9 @@ class Commands:
"""List unspent outputs. Returns the list of unspent transaction
outputs in your wallet."""
l = copy.deepcopy(self.wallet.get_spendable_coins(exclude_frozen = False))
- for i in l: i["value"] = str(Decimal(i["value"])/COIN)
+ for i in l:
+ v = i["value"]
+ i["value"] = float(v)/COIN if v is not None else None
return l
@command('n')
@@ -239,20 +241,21 @@ class Commands:
@command('wp')
def getprivatekeys(self, address):
- """Get the private keys of an address. Address must be in wallet."""
- return self.wallet.get_private_key(address, self.password)
+ """Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
+ is_list = type(address) is list
+ domain = address if is_list else [address]
+ out = [self.wallet.get_private_key(address, self.password) for address in domain]
+ return out if is_list else out[0]
@command('w')
def ismine(self, address):
"""Check if address is in wallet. Return true if and only address is in wallet"""
return self.wallet.is_mine(address)
- @command('wp')
- def dumpprivkeys(self, domain=None):
- """Dump private keys from your wallet"""
- if domain is None:
- domain = self.wallet.addresses(True)
- return [self.wallet.get_private_key(address, self.password) for address in domain]
+ @command('')
+ def dumpprivkeys(self):
+ """Deprecated."""
+ return "This command is deprecated. Use a pipe instead: 'electrum listaddresses | electrum getprivatekeys - '"
@command('')
def validateaddress(self, address):
@@ -322,6 +325,11 @@ class Commands:
"""Get master public key. Return your wallet\'s master public key(s)"""
return self.wallet.get_master_public_keys()
+ @command('wp')
+ def getmasterprivate(self):
+ """Get master private key. Return your wallet\'s master private key"""
+ return str(self.wallet.get_master_private_key(self.wallet.root_name, self.password))
+
@command('wp')
def getseed(self):
"""Get seed phrase. Print the generation seed of your wallet."""
@@ -348,15 +356,16 @@ class Commands:
@command('n')
def sweep(self, privkey, destination, tx_fee=None, nocheck=False):
- """Sweep private key. Returns a transaction that spends UTXOs from
+ """Sweep private keys. Returns a transaction that spends UTXOs from
privkey to a destination address. The transaction is not
broadcasted."""
+ privkeys = privkey if type(privkey) is list else [privkey]
self.nocheck = nocheck
dest = self._resolver(destination)
if tx_fee is None:
tx_fee = 0.0001
fee = int(Decimal(tx_fee)*COIN)
- return Transaction.sweep([privkey], self.network, dest, fee)
+ return Transaction.sweep(privkeys, self.network, dest, fee)
@command('wp')
def signmessage(self, address, message):
@@ -446,13 +455,18 @@ class Commands:
for item in self.wallet.get_history():
tx_hash, conf, value, timestamp, balance = item
try:
- time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
+ time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
except Exception:
time_str = "----"
-
label, is_default_label = self.wallet.get_label(tx_hash)
-
- out.append({'txid':tx_hash, 'date':"%16s"%time_str, 'label':label, 'value':format_satoshis(value), 'confirmations':conf})
+ out.append({
+ 'txid':tx_hash,
+ 'timestamp':timestamp,
+ 'date':"%16s"%time_str,
+ 'label':label,
+ 'value':float(value)/COIN if value is not None else None,
+ 'confirmations':conf}
+ )
return out
@command('w')
diff --git a/lib/mnemonic.py b/lib/mnemonic.py
index 0b8a5d150..14d5a8a51 100644
--- a/lib/mnemonic.py
+++ b/lib/mnemonic.py
@@ -90,6 +90,7 @@ filenames = {
'es':'spanish.txt',
'ja':'japanese.txt',
'pt':'portuguese.txt',
+ 'zh':'chinese_simplified.txt'
}
diff --git a/lib/network_proxy.py b/lib/network_proxy.py
index 4a4f6e4e2..950f7a496 100644
--- a/lib/network_proxy.py
+++ b/lib/network_proxy.py
@@ -40,7 +40,6 @@ class NetworkProxy(util.DaemonThread):
self.subscriptions = {}
self.debug = False
self.lock = threading.Lock()
- self.pending_transactions_for_notifications = []
self.callbacks = {}
if socket:
@@ -100,7 +99,10 @@ class NetworkProxy(util.DaemonThread):
self.servers = value
elif key == 'interfaces':
self.interfaces = value
- self.trigger_callback(key)
+ if key in ['status', 'updated']:
+ self.trigger_callback(key)
+ else:
+ self.trigger_callback(key, (value,))
return
msg_id = response.get('id')
@@ -227,8 +229,8 @@ class NetworkProxy(util.DaemonThread):
self.callbacks[event] = []
self.callbacks[event].append(callback)
- def trigger_callback(self, event):
+ def trigger_callback(self, event, params=()):
with self.lock:
callbacks = self.callbacks.get(event,[])[:]
if callbacks:
- [callback() for callback in callbacks]
+ [callback(*params) for callback in callbacks]
diff --git a/lib/synchronizer.py b/lib/synchronizer.py
index 5464299bd..08f96fdc2 100644
--- a/lib/synchronizer.py
+++ b/lib/synchronizer.py
@@ -126,16 +126,14 @@ class WalletSynchronizer():
except Exception:
self.print_msg("cannot deserialize transaction, skipping", tx_hash)
return
-
self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
self.requested_tx.remove((tx_hash, tx_height))
self.print_error("received tx:", tx_hash, len(tx.raw))
+ # callbacks
+ self.network.trigger_callback('new_transaction', (tx,))
if not self.requested_tx:
self.network.trigger_callback('updated')
- # Updated gets called too many times from other places as
- # well; if we used that signal we get the notification
- # three times
- self.network.trigger_callback("new_transaction")
+
def request_missing_txs(self, hist):
# "hist" is a list of [tx_hash, tx_height] lists
diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py
index a6cb38bf9..9a73b8e34 100644
--- a/lib/tests/test_bitcoin.py
+++ b/lib/tests/test_bitcoin.py
@@ -6,7 +6,8 @@ from lib.bitcoin import (
generator_secp256k1, point_to_ser, public_key_to_bc_address, EC_KEY,
bip32_root, bip32_public_derivation, bip32_private_derivation, pw_encode,
pw_decode, Hash, public_key_from_private_key, address_from_private_key,
- is_valid, is_private_key, xpub_from_xprv)
+ is_valid, is_private_key, xpub_from_xprv, is_new_seed, is_old_seed,
+ var_int, op_push)
try:
import ecdsa
@@ -135,6 +136,35 @@ class Test_bitcoin(unittest.TestCase):
result = xpub_from_xprv(xprv, testnet=True)
self.assertEqual(result, xpub)
+ def test_var_int(self):
+ for i in range(0xfd):
+ self.assertEqual(var_int(i), "{:02x}".format(i) )
+
+ self.assertEqual(var_int(0xfd), "fdfd00")
+ self.assertEqual(var_int(0xfe), "fdfe00")
+ self.assertEqual(var_int(0xff), "fdff00")
+ self.assertEqual(var_int(0x1234), "fd3412")
+ self.assertEqual(var_int(0xffff), "fdffff")
+ self.assertEqual(var_int(0x10000), "fe00000100")
+ self.assertEqual(var_int(0x12345678), "fe78563412")
+ self.assertEqual(var_int(0xffffffff), "feffffffff")
+ self.assertEqual(var_int(0x100000000), "ff0000000001000000")
+ self.assertEqual(var_int(0x0123456789abcdef), "ffefcdab8967452301")
+
+ def test_op_push(self):
+ self.assertEqual(op_push(0x00), '00')
+ self.assertEqual(op_push(0x12), '12')
+ self.assertEqual(op_push(0x4b), '4b')
+ self.assertEqual(op_push(0x4c), '4c4c')
+ self.assertEqual(op_push(0xfe), '4cfe')
+ self.assertEqual(op_push(0xff), '4dff00')
+ self.assertEqual(op_push(0x100), '4d0001')
+ self.assertEqual(op_push(0x1234), '4d3412')
+ self.assertEqual(op_push(0xfffe), '4dfeff')
+ self.assertEqual(op_push(0xffff), '4effff0000')
+ self.assertEqual(op_push(0x10000), '4e00000100')
+ self.assertEqual(op_push(0x12345678), '4e78563412')
+
class Test_keyImport(unittest.TestCase):
""" The keys used in this class are TEST keys from
@@ -161,3 +191,21 @@ class Test_keyImport(unittest.TestCase):
self.assertFalse(is_private_key(self.public_key_hex))
+class Test_seeds(unittest.TestCase):
+ """ Test old and new seeds. """
+
+ def test_new_seed(self):
+ seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able"
+ self.assertTrue(is_new_seed(seed))
+
+ seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform"
+ self.assertFalse(is_new_seed(seed))
+
+ def test_old_seed(self):
+ self.assertTrue(is_old_seed(" ".join(["like"] * 12)))
+ self.assertFalse(is_old_seed(" ".join(["like"] * 18)))
+ self.assertTrue(is_old_seed(" ".join(["like"] * 24)))
+ self.assertFalse(is_old_seed("not a seed"))
+
+ self.assertTrue(is_old_seed("0123456789ABCDEF" * 2))
+ self.assertTrue(is_old_seed("0123456789ABCDEF" * 4))
diff --git a/lib/tests/test_ripemd.py b/lib/tests/test_ripemd.py
new file mode 100644
index 000000000..cc8326088
--- /dev/null
+++ b/lib/tests/test_ripemd.py
@@ -0,0 +1,16 @@
+import unittest
+import random
+import hashlib
+
+from lib import ripemd
+
+class Test_RIPEMD160(unittest.TestCase):
+ """ Test pure Python implementation against standard library. """
+
+ def test_ripemd(self):
+ r = random.Random(0)
+ for i in range(128):
+ blob = bytearray([r.randrange(0, 256) for j in range(1024)])
+ h = hashlib.new('ripemd160')
+ h.update(blob)
+ self.assertEqual(h.hexdigest(), ripemd.new(blob).hexdigest())
diff --git a/lib/transaction.py b/lib/transaction.py
index 7368cba1b..f0632cfaf 100644
--- a/lib/transaction.py
+++ b/lib/transaction.py
@@ -481,7 +481,7 @@ class Transaction:
return self.raw
def __init__(self, raw):
- self.raw = raw
+ self.raw = raw.strip() if raw else None
self.inputs = None
def update(self, raw):
@@ -541,6 +541,7 @@ class Transaction:
@classmethod
def sweep(klass, privkeys, network, to_address, fee):
inputs = []
+ keypairs = {}
for privkey in privkeys:
pubkey = public_key_from_private_key(privkey)
address = address_from_private_key(privkey)
@@ -557,6 +558,7 @@ class Transaction:
item['signatures'] = [None]
item['num_sig'] = 1
inputs += u
+ keypairs[pubkey] = privkey
if not inputs:
return
@@ -564,7 +566,7 @@ class Transaction:
total = sum(i.get('value') for i in inputs) - fee
outputs = [('address', to_address, total)]
self = klass.from_io(inputs, outputs)
- self.sign({ pubkey:privkey })
+ self.sign(keypairs)
return self
@classmethod
diff --git a/lib/version.py b/lib/version.py
index b1943c26d..b06549adc 100644
--- a/lib/version.py
+++ b/lib/version.py
@@ -1,4 +1,4 @@
-ELECTRUM_VERSION = "2.4.1" # version of the client package
+ELECTRUM_VERSION = "2.4.2" # version of the client package
PROTOCOL_VERSION = '0.10' # protocol version requested
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
OLD_SEED_VERSION = 4 # electrum versions < 2.0
diff --git a/lib/wallet.py b/lib/wallet.py
index 8489d2dd5..367162e95 100644
--- a/lib/wallet.py
+++ b/lib/wallet.py
@@ -251,7 +251,7 @@ class Abstract_Wallet(object):
tx = self.transactions.get(tx_hash)
if tx is not None:
tx.deserialize()
- self.add_transaction(tx_hash, tx, tx_height)
+ self.add_transaction(tx_hash, tx)
if save:
self.storage.put('addr_history', self.history, True)
@@ -424,7 +424,9 @@ class Abstract_Wallet(object):
with self.lock:
self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos)
self.storage.put('verified_tx3', self.verified_tx, True)
- self.network.trigger_callback('updated')
+
+ conf, timestamp = self.get_confirmations(tx_hash)
+ self.network.trigger_callback('verified', (tx_hash, conf, timestamp))
def get_unverified_txs(self):
'''Returns a list of tuples (tx_hash, height) that are unverified and not beyond local height'''
@@ -693,7 +695,7 @@ class Abstract_Wallet(object):
print_error("found pay-to-pubkey address:", addr)
return addr
- def add_transaction(self, tx_hash, tx, tx_height):
+ def add_transaction(self, tx_hash, tx):
is_coinbase = tx.inputs[0].get('is_coinbase') == True
with self.transaction_lock:
# add inputs
@@ -744,7 +746,7 @@ class Abstract_Wallet(object):
# save
self.transactions[tx_hash] = tx
- def remove_transaction(self, tx_hash, tx_height):
+ def remove_transaction(self, tx_hash):
with self.transaction_lock:
print_error("removing tx from history", tx_hash)
#tx = self.transactions.pop(tx_hash)
@@ -770,13 +772,11 @@ class Abstract_Wallet(object):
def receive_tx_callback(self, tx_hash, tx, tx_height):
- self.add_transaction(tx_hash, tx, tx_height)
- #self.network.pending_transactions_for_notifications.append(tx)
+ self.add_transaction(tx_hash, tx)
self.add_unverified_tx(tx_hash, tx_height)
def receive_history_callback(self, addr, hist):
-
with self.lock:
old_hist = self.history.get(addr, [])
for tx_hash, height in old_hist:
@@ -784,7 +784,7 @@ class Abstract_Wallet(object):
# remove tx if it's not referenced in histories
self.tx_addr_hist[tx_hash].remove(addr)
if not self.tx_addr_hist[tx_hash]:
- self.remove_transaction(tx_hash, height)
+ self.remove_transaction(tx_hash)
self.history[addr] = hist
self.storage.put('addr_history', self.history, True)
@@ -800,7 +800,7 @@ class Abstract_Wallet(object):
tx = self.transactions.get(tx_hash)
if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get(tx_hash, {}).get(addr) is None:
tx.deserialize()
- self.add_transaction(tx_hash, tx, tx_height)
+ self.add_transaction(tx_hash, tx)
def get_history(self, domain=None):
@@ -878,6 +878,7 @@ class Abstract_Wallet(object):
# this method can be overloaded
return tx.get_fee()
+ @profiler
def estimated_fee(self, tx, fee_per_kb):
estimated_size = len(tx.serialize(-1))/2
fee = int(fee_per_kb * estimated_size / 1000.)
@@ -893,32 +894,45 @@ class Abstract_Wallet(object):
fee_per_kb = self.fee_per_kb(config)
amount = sum(map(lambda x:x[2], outputs))
- total = fee = 0
+ total = 0
inputs = []
tx = Transaction.from_io(inputs, outputs)
- # add old inputs first
+ fee = fixed_fee if fixed_fee is not None else 0
+ # add inputs, sorted by age
for item in coins:
v = item.get('value')
total += v
self.add_input_info(item)
tx.add_input(item)
# no need to estimate fee until we have reached desired amount
- if total < amount:
+ if total < amount + fee:
continue
fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
if total >= amount + fee:
break
else:
raise NotEnoughFunds()
- # remove unneeded inputs
+ # remove unneeded inputs.
+ removed = False
for item in sorted(tx.inputs, key=itemgetter('value')):
v = item.get('value')
if total - v >= amount + fee:
tx.inputs.remove(item)
total -= v
- fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
+ removed = True
+ continue
else:
break
+ if removed:
+ fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
+ for item in sorted(tx.inputs, key=itemgetter('value')):
+ v = item.get('value')
+ if total - v >= amount + fee:
+ tx.inputs.remove(item)
+ total -= v
+ fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
+ continue
+ break
print_error("using %d inputs"%len(tx.inputs))
# change address
diff --git a/lib/wordlist/chinese_simplified.txt b/lib/wordlist/chinese_simplified.txt
new file mode 100644
index 000000000..b90f1ed85
--- /dev/null
+++ b/lib/wordlist/chinese_simplified.txt
@@ -0,0 +1,2048 @@
+的
+一
+是
+在
+不
+了
+有
+和
+人
+这
+中
+大
+为
+上
+个
+国
+我
+以
+要
+他
+时
+来
+用
+们
+生
+到
+作
+地
+于
+出
+就
+分
+对
+成
+会
+可
+主
+发
+年
+动
+同
+工
+也
+能
+下
+过
+子
+说
+产
+种
+面
+而
+方
+后
+多
+定
+行
+学
+法
+所
+民
+得
+经
+十
+三
+之
+进
+着
+等
+部
+度
+家
+电
+力
+里
+如
+水
+化
+高
+自
+二
+理
+起
+小
+物
+现
+实
+加
+量
+都
+两
+体
+制
+机
+当
+使
+点
+从
+业
+本
+去
+把
+性
+好
+应
+开
+它
+合
+还
+因
+由
+其
+些
+然
+前
+外
+天
+政
+四
+日
+那
+社
+义
+事
+平
+形
+相
+全
+表
+间
+样
+与
+关
+各
+重
+新
+线
+内
+数
+正
+心
+反
+你
+明
+看
+原
+又
+么
+利
+比
+或
+但
+质
+气
+第
+向
+道
+命
+此
+变
+条
+只
+没
+结
+解
+问
+意
+建
+月
+公
+无
+系
+军
+很
+情
+者
+最
+立
+代
+想
+已
+通
+并
+提
+直
+题
+党
+程
+展
+五
+果
+料
+象
+员
+革
+位
+入
+常
+文
+总
+次
+品
+式
+活
+设
+及
+管
+特
+件
+长
+求
+老
+头
+基
+资
+边
+流
+路
+级
+少
+图
+山
+统
+接
+知
+较
+将
+组
+见
+计
+别
+她
+手
+角
+期
+根
+论
+运
+农
+指
+几
+九
+区
+强
+放
+决
+西
+被
+干
+做
+必
+战
+先
+回
+则
+任
+取
+据
+处
+队
+南
+给
+色
+光
+门
+即
+保
+治
+北
+造
+百
+规
+热
+领
+七
+海
+口
+东
+导
+器
+压
+志
+世
+金
+增
+争
+济
+阶
+油
+思
+术
+极
+交
+受
+联
+什
+认
+六
+共
+权
+收
+证
+改
+清
+美
+再
+采
+转
+更
+单
+风
+切
+打
+白
+教
+速
+花
+带
+安
+场
+身
+车
+例
+真
+务
+具
+万
+每
+目
+至
+达
+走
+积
+示
+议
+声
+报
+斗
+完
+类
+八
+离
+华
+名
+确
+才
+科
+张
+信
+马
+节
+话
+米
+整
+空
+元
+况
+今
+集
+温
+传
+土
+许
+步
+群
+广
+石
+记
+需
+段
+研
+界
+拉
+林
+律
+叫
+且
+究
+观
+越
+织
+装
+影
+算
+低
+持
+音
+众
+书
+布
+复
+容
+儿
+须
+际
+商
+非
+验
+连
+断
+深
+难
+近
+矿
+千
+周
+委
+素
+技
+备
+半
+办
+青
+省
+列
+习
+响
+约
+支
+般
+史
+感
+劳
+便
+团
+往
+酸
+历
+市
+克
+何
+除
+消
+构
+府
+称
+太
+准
+精
+值
+号
+率
+族
+维
+划
+选
+标
+写
+存
+候
+毛
+亲
+快
+效
+斯
+院
+查
+江
+型
+眼
+王
+按
+格
+养
+易
+置
+派
+层
+片
+始
+却
+专
+状
+育
+厂
+京
+识
+适
+属
+圆
+包
+火
+住
+调
+满
+县
+局
+照
+参
+红
+细
+引
+听
+该
+铁
+价
+严
+首
+底
+液
+官
+德
+随
+病
+苏
+失
+尔
+死
+讲
+配
+女
+黄
+推
+显
+谈
+罪
+神
+艺
+呢
+席
+含
+企
+望
+密
+批
+营
+项
+防
+举
+球
+英
+氧
+势
+告
+李
+台
+落
+木
+帮
+轮
+破
+亚
+师
+围
+注
+远
+字
+材
+排
+供
+河
+态
+封
+另
+施
+减
+树
+溶
+怎
+止
+案
+言
+士
+均
+武
+固
+叶
+鱼
+波
+视
+仅
+费
+紧
+爱
+左
+章
+早
+朝
+害
+续
+轻
+服
+试
+食
+充
+兵
+源
+判
+护
+司
+足
+某
+练
+差
+致
+板
+田
+降
+黑
+犯
+负
+击
+范
+继
+兴
+似
+余
+坚
+曲
+输
+修
+故
+城
+夫
+够
+送
+笔
+船
+占
+右
+财
+吃
+富
+春
+职
+觉
+汉
+画
+功
+巴
+跟
+虽
+杂
+飞
+检
+吸
+助
+升
+阳
+互
+初
+创
+抗
+考
+投
+坏
+策
+古
+径
+换
+未
+跑
+留
+钢
+曾
+端
+责
+站
+简
+述
+钱
+副
+尽
+帝
+射
+草
+冲
+承
+独
+令
+限
+阿
+宣
+环
+双
+请
+超
+微
+让
+控
+州
+良
+轴
+找
+否
+纪
+益
+依
+优
+顶
+础
+载
+倒
+房
+突
+坐
+粉
+敌
+略
+客
+袁
+冷
+胜
+绝
+析
+块
+剂
+测
+丝
+协
+诉
+念
+陈
+仍
+罗
+盐
+友
+洋
+错
+苦
+夜
+刑
+移
+频
+逐
+靠
+混
+母
+短
+皮
+终
+聚
+汽
+村
+云
+哪
+既
+距
+卫
+停
+烈
+央
+察
+烧
+迅
+境
+若
+印
+洲
+刻
+括
+激
+孔
+搞
+甚
+室
+待
+核
+校
+散
+侵
+吧
+甲
+游
+久
+菜
+味
+旧
+模
+湖
+货
+损
+预
+阻
+毫
+普
+稳
+乙
+妈
+植
+息
+扩
+银
+语
+挥
+酒
+守
+拿
+序
+纸
+医
+缺
+雨
+吗
+针
+刘
+啊
+急
+唱
+误
+训
+愿
+审
+附
+获
+茶
+鲜
+粮
+斤
+孩
+脱
+硫
+肥
+善
+龙
+演
+父
+渐
+血
+欢
+械
+掌
+歌
+沙
+刚
+攻
+谓
+盾
+讨
+晚
+粒
+乱
+燃
+矛
+乎
+杀
+药
+宁
+鲁
+贵
+钟
+煤
+读
+班
+伯
+香
+介
+迫
+句
+丰
+培
+握
+兰
+担
+弦
+蛋
+沉
+假
+穿
+执
+答
+乐
+谁
+顺
+烟
+缩
+征
+脸
+喜
+松
+脚
+困
+异
+免
+背
+星
+福
+买
+染
+井
+概
+慢
+怕
+磁
+倍
+祖
+皇
+促
+静
+补
+评
+翻
+肉
+践
+尼
+衣
+宽
+扬
+棉
+希
+伤
+操
+垂
+秋
+宜
+氢
+套
+督
+振
+架
+亮
+末
+宪
+庆
+编
+牛
+触
+映
+雷
+销
+诗
+座
+居
+抓
+裂
+胞
+呼
+娘
+景
+威
+绿
+晶
+厚
+盟
+衡
+鸡
+孙
+延
+危
+胶
+屋
+乡
+临
+陆
+顾
+掉
+呀
+灯
+岁
+措
+束
+耐
+剧
+玉
+赵
+跳
+哥
+季
+课
+凯
+胡
+额
+款
+绍
+卷
+齐
+伟
+蒸
+殖
+永
+宗
+苗
+川
+炉
+岩
+弱
+零
+杨
+奏
+沿
+露
+杆
+探
+滑
+镇
+饭
+浓
+航
+怀
+赶
+库
+夺
+伊
+灵
+税
+途
+灭
+赛
+归
+召
+鼓
+播
+盘
+裁
+险
+康
+唯
+录
+菌
+纯
+借
+糖
+盖
+横
+符
+私
+努
+堂
+域
+枪
+润
+幅
+哈
+竟
+熟
+虫
+泽
+脑
+壤
+碳
+欧
+遍
+侧
+寨
+敢
+彻
+虑
+斜
+薄
+庭
+纳
+弹
+饲
+伸
+折
+麦
+湿
+暗
+荷
+瓦
+塞
+床
+筑
+恶
+户
+访
+塔
+奇
+透
+梁
+刀
+旋
+迹
+卡
+氯
+遇
+份
+毒
+泥
+退
+洗
+摆
+灰
+彩
+卖
+耗
+夏
+择
+忙
+铜
+献
+硬
+予
+繁
+圈
+雪
+函
+亦
+抽
+篇
+阵
+阴
+丁
+尺
+追
+堆
+雄
+迎
+泛
+爸
+楼
+避
+谋
+吨
+野
+猪
+旗
+累
+偏
+典
+馆
+索
+秦
+脂
+潮
+爷
+豆
+忽
+托
+惊
+塑
+遗
+愈
+朱
+替
+纤
+粗
+倾
+尚
+痛
+楚
+谢
+奋
+购
+磨
+君
+池
+旁
+碎
+骨
+监
+捕
+弟
+暴
+割
+贯
+殊
+释
+词
+亡
+壁
+顿
+宝
+午
+尘
+闻
+揭
+炮
+残
+冬
+桥
+妇
+警
+综
+招
+吴
+付
+浮
+遭
+徐
+您
+摇
+谷
+赞
+箱
+隔
+订
+男
+吹
+园
+纷
+唐
+败
+宋
+玻
+巨
+耕
+坦
+荣
+闭
+湾
+键
+凡
+驻
+锅
+救
+恩
+剥
+凝
+碱
+齿
+截
+炼
+麻
+纺
+禁
+废
+盛
+版
+缓
+净
+睛
+昌
+婚
+涉
+筒
+嘴
+插
+岸
+朗
+庄
+街
+藏
+姑
+贸
+腐
+奴
+啦
+惯
+乘
+伙
+恢
+匀
+纱
+扎
+辩
+耳
+彪
+臣
+亿
+璃
+抵
+脉
+秀
+萨
+俄
+网
+舞
+店
+喷
+纵
+寸
+汗
+挂
+洪
+贺
+闪
+柬
+爆
+烯
+津
+稻
+墙
+软
+勇
+像
+滚
+厘
+蒙
+芳
+肯
+坡
+柱
+荡
+腿
+仪
+旅
+尾
+轧
+冰
+贡
+登
+黎
+削
+钻
+勒
+逃
+障
+氨
+郭
+峰
+币
+港
+伏
+轨
+亩
+毕
+擦
+莫
+刺
+浪
+秘
+援
+株
+健
+售
+股
+岛
+甘
+泡
+睡
+童
+铸
+汤
+阀
+休
+汇
+舍
+牧
+绕
+炸
+哲
+磷
+绩
+朋
+淡
+尖
+启
+陷
+柴
+呈
+徒
+颜
+泪
+稍
+忘
+泵
+蓝
+拖
+洞
+授
+镜
+辛
+壮
+锋
+贫
+虚
+弯
+摩
+泰
+幼
+廷
+尊
+窗
+纲
+弄
+隶
+疑
+氏
+宫
+姐
+震
+瑞
+怪
+尤
+琴
+循
+描
+膜
+违
+夹
+腰
+缘
+珠
+穷
+森
+枝
+竹
+沟
+催
+绳
+忆
+邦
+剩
+幸
+浆
+栏
+拥
+牙
+贮
+礼
+滤
+钠
+纹
+罢
+拍
+咱
+喊
+袖
+埃
+勤
+罚
+焦
+潜
+伍
+墨
+欲
+缝
+姓
+刊
+饱
+仿
+奖
+铝
+鬼
+丽
+跨
+默
+挖
+链
+扫
+喝
+袋
+炭
+污
+幕
+诸
+弧
+励
+梅
+奶
+洁
+灾
+舟
+鉴
+苯
+讼
+抱
+毁
+懂
+寒
+智
+埔
+寄
+届
+跃
+渡
+挑
+丹
+艰
+贝
+碰
+拔
+爹
+戴
+码
+梦
+芽
+熔
+赤
+渔
+哭
+敬
+颗
+奔
+铅
+仲
+虎
+稀
+妹
+乏
+珍
+申
+桌
+遵
+允
+隆
+螺
+仓
+魏
+锐
+晓
+氮
+兼
+隐
+碍
+赫
+拨
+忠
+肃
+缸
+牵
+抢
+博
+巧
+壳
+兄
+杜
+讯
+诚
+碧
+祥
+柯
+页
+巡
+矩
+悲
+灌
+龄
+伦
+票
+寻
+桂
+铺
+圣
+恐
+恰
+郑
+趣
+抬
+荒
+腾
+贴
+柔
+滴
+猛
+阔
+辆
+妻
+填
+撤
+储
+签
+闹
+扰
+紫
+砂
+递
+戏
+吊
+陶
+伐
+喂
+疗
+瓶
+婆
+抚
+臂
+摸
+忍
+虾
+蜡
+邻
+胸
+巩
+挤
+偶
+弃
+槽
+劲
+乳
+邓
+吉
+仁
+烂
+砖
+租
+乌
+舰
+伴
+瓜
+浅
+丙
+暂
+燥
+橡
+柳
+迷
+暖
+牌
+秧
+胆
+详
+簧
+踏
+瓷
+谱
+呆
+宾
+糊
+洛
+辉
+愤
+竞
+隙
+怒
+粘
+乃
+绪
+肩
+籍
+敏
+涂
+熙
+皆
+侦
+悬
+掘
+享
+纠
+醒
+狂
+锁
+淀
+恨
+牲
+霸
+爬
+赏
+逆
+玩
+陵
+祝
+秒
+浙
+貌
+役
+彼
+悉
+鸭
+趋
+凤
+晨
+畜
+辈
+秩
+卵
+署
+梯
+炎
+滩
+棋
+驱
+筛
+峡
+冒
+啥
+寿
+译
+浸
+泉
+帽
+迟
+硅
+疆
+贷
+漏
+稿
+冠
+嫩
+胁
+芯
+牢
+叛
+蚀
+奥
+鸣
+岭
+羊
+凭
+串
+塘
+绘
+酵
+融
+盆
+锡
+庙
+筹
+冻
+辅
+摄
+袭
+筋
+拒
+僚
+旱
+钾
+鸟
+漆
+沈
+眉
+疏
+添
+棒
+穗
+硝
+韩
+逼
+扭
+侨
+凉
+挺
+碗
+栽
+炒
+杯
+患
+馏
+劝
+豪
+辽
+勃
+鸿
+旦
+吏
+拜
+狗
+埋
+辊
+掩
+饮
+搬
+骂
+辞
+勾
+扣
+估
+蒋
+绒
+雾
+丈
+朵
+姆
+拟
+宇
+辑
+陕
+雕
+偿
+蓄
+崇
+剪
+倡
+厅
+咬
+驶
+薯
+刷
+斥
+番
+赋
+奉
+佛
+浇
+漫
+曼
+扇
+钙
+桃
+扶
+仔
+返
+俗
+亏
+腔
+鞋
+棱
+覆
+框
+悄
+叔
+撞
+骗
+勘
+旺
+沸
+孤
+吐
+孟
+渠
+屈
+疾
+妙
+惜
+仰
+狠
+胀
+谐
+抛
+霉
+桑
+岗
+嘛
+衰
+盗
+渗
+脏
+赖
+涌
+甜
+曹
+阅
+肌
+哩
+厉
+烃
+纬
+毅
+昨
+伪
+症
+煮
+叹
+钉
+搭
+茎
+笼
+酷
+偷
+弓
+锥
+恒
+杰
+坑
+鼻
+翼
+纶
+叙
+狱
+逮
+罐
+络
+棚
+抑
+膨
+蔬
+寺
+骤
+穆
+冶
+枯
+册
+尸
+凸
+绅
+坯
+牺
+焰
+轰
+欣
+晋
+瘦
+御
+锭
+锦
+丧
+旬
+锻
+垄
+搜
+扑
+邀
+亭
+酯
+迈
+舒
+脆
+酶
+闲
+忧
+酚
+顽
+羽
+涨
+卸
+仗
+陪
+辟
+惩
+杭
+姚
+肚
+捉
+飘
+漂
+昆
+欺
+吾
+郎
+烷
+汁
+呵
+饰
+萧
+雅
+邮
+迁
+燕
+撒
+姻
+赴
+宴
+烦
+债
+帐
+斑
+铃
+旨
+醇
+董
+饼
+雏
+姿
+拌
+傅
+腹
+妥
+揉
+贤
+拆
+歪
+葡
+胺
+丢
+浩
+徽
+昂
+垫
+挡
+览
+贪
+慰
+缴
+汪
+慌
+冯
+诺
+姜
+谊
+凶
+劣
+诬
+耀
+昏
+躺
+盈
+骑
+乔
+溪
+丛
+卢
+抹
+闷
+咨
+刮
+驾
+缆
+悟
+摘
+铒
+掷
+颇
+幻
+柄
+惠
+惨
+佳
+仇
+腊
+窝
+涤
+剑
+瞧
+堡
+泼
+葱
+罩
+霍
+捞
+胎
+苍
+滨
+俩
+捅
+湘
+砍
+霞
+邵
+萄
+疯
+淮
+遂
+熊
+粪
+烘
+宿
+档
+戈
+驳
+嫂
+裕
+徙
+箭
+捐
+肠
+撑
+晒
+辨
+殿
+莲
+摊
+搅
+酱
+屏
+疫
+哀
+蔡
+堵
+沫
+皱
+畅
+叠
+阁
+莱
+敲
+辖
+钩
+痕
+坝
+巷
+饿
+祸
+丘
+玄
+溜
+曰
+逻
+彭
+尝
+卿
+妨
+艇
+吞
+韦
+怨
+矮
+歇
diff --git a/lib/x509.py b/lib/x509.py
index 3f722a166..11716022e 100644
--- a/lib/x509.py
+++ b/lib/x509.py
@@ -190,68 +190,3 @@ def load_certificates(ca_path):
ca_keyID[x.get_keyID()] = fp
return ca_list, ca_keyID
-
-
-def int_to_bytestr(i):
- s = chr(i % 256)
- while i > 256:
- i >>= 8
- s = chr(i % 256) + s
- return s
-
-def create_csr(commonName, challenge, k):
- from bitcoin import point_to_ser
- private_key = ecdsa.SigningKey.from_string(k, curve = ecdsa.SECP256k1)
- public_key = private_key.get_verifying_key()
- pubkey = point_to_ser(public_key.pubkey.point, False)
- asn1_type_table = {
- 'BOOLEAN': 0x01, 'INTEGER': 0x02,
- 'BIT STRING': 0x03, 'OCTET STRING': 0x04,
- 'NULL': 0x05, 'OBJECT IDENTIFIER': 0x06,
- 'SEQUENCE': 0x30, 'SET': 0x31,
- 'PrintableString': 0x13, 'IA5String': 0x16,
- 'UTCTime': 0x17, 'ENUMERATED': 0x0A,
- 'UTF8String': 0x0C, 'PrintableString': 0x13,
- }
- def x(t, s):
- c = asn1_type_table[t] & 0x3f if type(t) == str else t
- l = len(s)
- if l < 128:
- ls = chr(l)
- else:
- n = int_to_bytestr(l)
- ls = chr(len(n) + 128) + n
- return chr(c) + ls + s
- x_int = lambda i: x('INTEGER', int_to_bytestr(i))
- x_seq = lambda *items: x('SEQUENCE', ''.join(items))
- x_bitstring = lambda s: x('BIT STRING', s)
- x_utf8 = lambda s: x('UTF8String', s)
- x_set = lambda *items: x('SET', ''.join(items))
- x_printable = lambda s: x('PrintableString', s)
- x_obj = lambda oid: x('OBJECT IDENTIFIER', encode_OID(oid))
- body = x_seq(
- x_int(0),
- x_seq(
- x_set(x_seq(x_obj('2.5.4.3'), x_utf8(commonName)))
- ),
- x_seq(
- x_seq(
- x_obj('1.2.840.10045.2.1'),
- x_obj('1.3.132.0.10')
- ),
- x_bitstring(chr(0) + pubkey)
- ),
- x(0xa0, x_seq(x_obj('1.2.840.113549.1.9.7'), x_set(x_utf8(challenge)))
- )
- )
- signature = private_key.sign_deterministic(body, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der)
- assert public_key.verify(signature, body, hashfunc=hashlib.sha256, sigdecode = ecdsa.util.sigdecode_der)
- csr = x_seq(
- body,
- x_seq(x_obj(ALGO_ECDSA_SHA256)),
- x_bitstring(chr(0) + signature)
- )
- return csr
-
-
-
diff --git a/plugins/__init__.py b/plugins/__init__.py
index f95519495..8e21ffc9f 100644
--- a/plugins/__init__.py
+++ b/plugins/__init__.py
@@ -29,11 +29,11 @@ descriptions = [
},
{
'name': 'btchipwallet',
- 'fullname': _('BTChip Wallet'),
- 'description': _('Provides support for BTChip hardware wallet'),
- 'requires': [('btchip', 'github.com/btchip/btchip-python')],
+ 'fullname': _('Ledger Wallet'),
+ 'description': _('Provides support for Ledger hardware wallet'),
+ 'requires': [('btchip', 'github.com/ledgerhq/btchip-python')],
'requires_wallet_type': ['btchip'],
- 'registers_wallet_type': ('hardware', 'btchip', _("BTChip wallet")),
+ 'registers_wallet_type': ('hardware', 'btchip', _("Ledger wallet")),
'available_for': ['qt', 'cmdline'],
},
{
diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py
index beb4cde33..26ff2c9ef 100644
--- a/plugins/btchipwallet.py
+++ b/plugins/btchipwallet.py
@@ -88,12 +88,16 @@ class Plugin(BasePlugin):
self.handler = BTChipQTHandler(self.window.app)
if self.btchip_is_connected():
if not self.wallet.check_proper_device():
- QMessageBox.information(self.window, _('Error'), _("This wallet does not match your BTChip device"), _('OK'))
+ QMessageBox.information(self.window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
self.wallet.force_watching_only = True
else:
- QMessageBox.information(self.window, _('Error'), _("BTChip device not detected.\nContinuing in watching-only mode."), _('OK'))
+ QMessageBox.information(self.window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
self.wallet.force_watching_only = True
+ @hook
+ def installwizard_load_wallet(self, wallet, window):
+ self.load_wallet(wallet, window)
+
@hook
def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != 'btchip':
@@ -196,9 +200,9 @@ class BTChipWallet(BIP32_HD_Wallet):
# Immediately prompts for the PIN
remaining_attempts = self.client.getVerifyPinRemainingAttempts()
if remaining_attempts <> 1:
- msg = "Enter your BTChip PIN - remaining attempts : " + str(remaining_attempts)
+ msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)
else:
- msg = "Enter your BTChip PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped."
+ msg = "Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped."
confirmed, p, pin = self.password_dialog(msg)
if not confirmed:
aborted = True
@@ -224,7 +228,7 @@ class BTChipWallet(BIP32_HD_Wallet):
pass
self.client = None
if not aborted:
- raise Exception("Could not connect to your BTChip dongle. Please verify access permissions, PIN, or unplug the dongle and plug it again")
+ raise Exception("Could not connect to your Ledger wallet. Please verify access permissions, PIN, or unplug the dongle and plug it again")
else:
raise e
self.client.bad = False
@@ -320,7 +324,7 @@ class BTChipWallet(BIP32_HD_Wallet):
signature = self.get_client().signMessageSign(pin)
except BTChipException, e:
if e.sw == 0x6a80:
- self.give_error("Unfortunately, this message cannot be signed by BTChip. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.")
+ self.give_error("Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.")
else:
self.give_error(e, True)
except Exception, e:
@@ -419,7 +423,7 @@ class BTChipWallet(BIP32_HD_Wallet):
pin2 = ""
for keycardIndex in range(len(outputData['keycardData'])):
msg = "Do not enter your device PIN here !\r\n\r\n" + \
- "Your BTChip wants to talk to you and tell you a unique second factor code.\r\n" + \
+ "Your Ledger Wallet wants to talk to you and tell you a unique second factor code.\r\n" + \
"For this to work, please match the character between stars of the output address using your security card\r\n\r\n" + \
"Output address : "
for index in range(len(output)):
@@ -494,8 +498,8 @@ class BTChipWallet(BIP32_HD_Wallet):
def password_dialog(self, msg=None):
if not msg:
msg = _("Do not enter your device PIN here !\r\n\r\n" \
- "Your BTChip wants to talk to you and tell you a unique second factor code.\r\n" \
- "For this to work, please open a text editor (on a different computer / device if you believe this computer is compromised) and put your cursor into it, unplug your BTChip and plug it back in.\r\n" \
+ "Your Ledger Wallet wants to talk to you and tell you a unique second factor code.\r\n" \
+ "For this to work, please open a text editor (on a different computer / device if you believe this computer is compromised) and put your cursor into it, unplug your Ledger Wallet and plug it back in.\r\n" \
"It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor.\r\n\r\n" \
"Check that summary and then enter the second factor code here.\r\n" \
"Before clicking OK, re-plug the device once more (unplug it and plug it again if you read the second factor code on the same computer)")
@@ -528,7 +532,7 @@ class BTChipQTHandler:
return self.response
def auth_dialog(self):
- response = QInputDialog.getText(None, "BTChip Authentication", self.message, QLineEdit.Password)
+ response = QInputDialog.getText(None, "Ledger Wallet Authentication", self.message, QLineEdit.Password)
if not response[1]:
self.response = None
else:
@@ -538,7 +542,7 @@ class BTChipQTHandler:
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
- self.d.setWindowTitle('BTChip')
+ self.d.setWindowTitle('Ledger')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
diff --git a/plugins/trezor.py b/plugins/trezor.py
index 1ab45d12f..ad46679c2 100644
--- a/plugins/trezor.py
+++ b/plugins/trezor.py
@@ -22,7 +22,8 @@ from electrum.util import print_error, print_msg
from electrum.wallet import pw_decode, bip32_private_derivation, bip32_root
from electrum_gui.qt.util import *
-from electrum_gui.qt.main_window import StatusBarButton
+from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
+from electrum_gui.qt.installwizard import InstallWizard
try:
from trezorlib.client import types
@@ -130,9 +131,10 @@ class Plugin(BasePlugin):
self.window = window
self.wallet.plugin = self
self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), self.settings_dialog)
- self.window.statusBar().addPermanentWidget(self.trezor_button)
+ if type(window) is ElectrumWindow:
+ self.window.statusBar().addPermanentWidget(self.trezor_button)
if self.handler is None:
- self.handler = TrezorQtHandler(self.window.app)
+ self.handler = TrezorQtHandler(self.window)
try:
self.get_client().ping('t')
except BaseException as e:
@@ -145,7 +147,8 @@ class Plugin(BasePlugin):
@hook
def close_wallet(self):
- self.window.statusBar().removeWidget(self.trezor_button)
+ if type(self.window) is ElectrumWindow:
+ self.window.statusBar().removeWidget(self.trezor_button)
@hook
def installwizard_load_wallet(self, wallet, window):
@@ -623,18 +626,21 @@ class TrezorQtHandler:
self.done.set()
def passphrase_dialog(self):
- from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
- d = QDialog()
- d.setModal(1)
- d.setLayout(make_password_dialog(d, None, self.message, False))
- confirmed, p, passphrase = run_password_dialog(d, None, None)
- if not confirmed:
- QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
- self.passphrase = None
+ if type(self.win) is ElectrumWindow:
+ passphrase = self.win.password_dialog(_("Please enter your Trezor passphrase"))
+ self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
- if passphrase is None:
- passphrase = '' # Even blank string is valid Trezor passphrase
- self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase))
+ assert type(self.win) is InstallWizard
+ from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
+ d = QDialog()
+ d.setModal(1)
+ d.setLayout(make_password_dialog(d, None, self.message, False))
+ confirmed, p, passphrase = run_password_dialog(d, None, None)
+ if not confirmed:
+ QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
+ self.passphrase = None
+ else:
+ self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
diff --git a/scripts/authenticator.py b/scripts/authenticator.py
index 9eed5dd78..fcb5add89 100644
--- a/scripts/authenticator.py
+++ b/scripts/authenticator.py
@@ -290,6 +290,8 @@ class Authenticator:
mpk = wallet.get_master_public_key()
self.show_qr(mpk)
self.show_title('master public key')
+ droid.setClipboard(mpk)
+ droid.makeToast("Master public key copied to clipboard")
elif event["name"] == "scan":
r = droid.scanBarcode()
diff --git a/setup-release.py b/setup-release.py
index de778f83e..3f9463349 100644
--- a/setup-release.py
+++ b/setup-release.py
@@ -1,5 +1,5 @@
"""
-py2app/py2exe build script for Electrum Litecoin
+py2app/py2exe build script for Electrum
Usage (Mac OS X):
python setup.py py2app
@@ -37,7 +37,7 @@ if sys.platform == 'darwin':
app=[mainscript],
options=dict(py2app=dict(argv_emulation=False,
includes=['PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork', 'sip'],
- packages=['lib', 'gui', 'plugins'],
+ packages=['lib', 'gui', 'plugins', 'packages'],
iconfile='electrum.icns',
plist=plist,
resources=["icons"])),