Merge branch 'master' of https://github.com/spesmilo/electrum
This commit is contained in:
commit
257fd9bfb0
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
|
@ -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>
|
44
electrum
44
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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -90,6 +90,7 @@ filenames = {
|
|||
'es':'spanish.txt',
|
||||
'ja':'japanese.txt',
|
||||
'pt':'portuguese.txt',
|
||||
'zh':'chinese_simplified.txt'
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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())
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
65
lib/x509.py
65
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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"])),
|
||||
|
|
Loading…
Reference in New Issue