This commit is contained in:
Darin Stanchfield 2015-08-20 13:14:33 -07:00
commit 257fd9bfb0
31 changed files with 2379 additions and 430 deletions

View File

@ -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

View File

@ -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()

View File

@ -1,60 +0,0 @@
<html>
<body>
<div style="width:35em;margin-left:5em;">
<h2>Electrum for Android</h2>
This page explains how to install Electrum on Android devices.<br/><br/>
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. <br/><br/>
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
<a href="http://maketecheasier.com/install-applications-without-the-market/2011/01/28">
this link</a>.
<h3>1. Download and install Google Scripting Layer for Android</h3>
You can get
it <a href="http://code.google.com/p/android-scripting/wiki/Unofficial">here</a>,
or by scanning the following qr code: <br/>
<img src="http://chart.apis.google.com/chart?cht=qr&chs=150x150&chld=L&choe=UTF-8&chl=http%3A%2F%2Fwww.mithril.com.au%2Fandroid%2Fsl4a_r5x.apk"
/>
<h3>2. Download and install Python for Android</h3>
You can get
it <a href="http://code.google.com/p/python-for-android/downloads/detail?name=PythonForAndroid_r5.apk">here</a>,
or by scanning the following qr code: <br/>
<img src="http://chart.apis.google.com/chart?cht=qr&chs=200x200&chld=L&choe=UTF-8&chl=http%3A%2F%2Fpython-for-android.googlecode.com%2Ffiles%2FPythonForAndroid_r5.apk"/>
<br/>
Once you have installed the apk, launch the Python for Android application and click 'install'
<h3>3. Download the Electrum install script</h3>
Download <a href="e4a_install.py">e4a_install.py</a> and install it in your sl4a/scripts directory.
You can do it manually, or from QR code, as follows:
<pre>
1. Launch SL4A.
2. Press the Menu button.
3. Tap Add.
4. Tap Scan Barcode.
5. Scan the following QRcode:
</pre>
<img src="http://chart.apis.google.com/chart?cht=qr&chs=300x300&chld=L&choe=UTF-8&chl=e4a_install.py%0Aimport+urllib%2C+zipfile%2C+os%0Ap%3D%22http%3A%2F%2Fecdsa.org%2Felectrum%2Fe4a%22%0An%3D%22Electrum-0.43d%22%0Anz%3Dn%2B%22.zip%22%0Aurllib.urlretrieve(p%2Cnz)%0Azipfile.ZipFile(nz).extractall()%0Aos.rename(n%2C'scripts%2F'%2Bn)"
/>
<pre>This will install a script named e4a_install.py</pre>
<h3>4. Download and install Electrum</h3>
<pre>
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'
</pre>
</body>
</html>

View File

@ -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"

View File

@ -1,71 +0,0 @@
<html>
<head>
This is the documentation for the Electrum Console.<br/>
</head>
<body>
<div style="width:45em">
<br/>
Most Electrum command-line commands are also available in the console. <br/>
The results are Python objects, even though they are
sometimes rendered as JSON for clarity.<br/>
<br/>
Let us call <tt>listunspent()</tt>, to see the list of unspent outputs in the wallet:
<pre>
>> 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
}
]
</pre>
Note that the result is rendered as JSON. <br/>
However, if we save it to a Python variable, it is rendered as a Python object:
<pre>
>> 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'}]
</pre>
<br/>
This makes it possible to combine Electrum commands with Python.<br/>
For example, let us pick only the addresses in the previous result:
<pre>
>> map(lambda x:x.get('address'), listunspent())
[
"12cmY5RHRgx8KkUKASDcDYRotget9FNso3",
"1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF"
]
</pre>
Here we combine two commands, <tt>listunspent</tt>
and <tt>dumpprivkeys</tt>, in order to dump the private keys of all adresses that have unspent outputs:
<pre>
>> dumpprivkeys( map(lambda x:x.get('address'), listunspent()) )
{
"12cmY5RHRgx8KkUKASDcDYRotget9FNso3": "***************************************************",
"1GavSCND6TB7HuCnJSTEbHEmCctNGeJwXF": "***************************************************"
}
</pre>
Note that <tt>dumpprivkey</tt> will ask for your password if your
wallet is encrypted.
<br/>
The GUI methods can be accessed through the <tt>gui</tt> variable.
For example, you can display a QR code from a string using
gui.show_qrcode.
Example:
<pre>
gui.show_qrcode(dumpprivkey(listunspent()[0]['address']))
</pre>
</div>
</body>
</html>

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
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'

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -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')

View File

@ -90,6 +90,7 @@ filenames = {
'es':'spanish.txt',
'ja':'japanese.txt',
'pt':'portuguese.txt',
'zh':'chinese_simplified.txt'
}

View File

@ -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]

View File

@ -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

View File

@ -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))

16
lib/tests/test_ripemd.py Normal file
View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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'],
},
{

View File

@ -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)

View File

@ -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):

View File

@ -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()

View File

@ -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"])),