From b8773178a19a0dbd1b154acb5903d33ab73bae8e Mon Sep 17 00:00:00 2001 From: thomasv Date: Thu, 10 May 2012 14:38:49 +0200 Subject: [PATCH] setup package in lib subdirectory --- MANIFEST.in | 1 + README | 21 +- blocks | 4 +- bmp.py | 222 ------- electrum | 10 +- gui.py | 1297 ---------------------------------------- gui_qt.py | 1222 -------------------------------------- interface.py | 393 ------------- lib/__init__.py | 3 + lib/bmp.py | 222 +++++++ lib/gui.py | 1297 ++++++++++++++++++++++++++++++++++++++++ lib/gui_qt.py | 1222 ++++++++++++++++++++++++++++++++++++++ lib/interface.py | 393 +++++++++++++ lib/mnemonic.py | 1689 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/msqr.py | 94 +++ lib/pyqrnative.py | 962 ++++++++++++++++++++++++++++++ lib/ripemd.py | 399 +++++++++++++ lib/version.py | 2 + lib/wallet.py | 963 ++++++++++++++++++++++++++++++ mnemonic.py | 1689 ----------------------------------------------------- msqr.py | 94 --- peers | 4 +- pyqrnative.py | 962 ------------------------------ ripemd.py | 399 ------------- setup.py | 7 +- version.py | 2 - wallet.py | 963 ------------------------------ watch_address | 6 +- 28 files changed, 7278 insertions(+), 7264 deletions(-) delete mode 100644 bmp.py delete mode 100644 gui.py delete mode 100644 gui_qt.py delete mode 100644 interface.py create mode 100644 lib/__init__.py create mode 100644 lib/bmp.py create mode 100644 lib/gui.py create mode 100644 lib/gui_qt.py create mode 100644 lib/interface.py create mode 100644 lib/mnemonic.py create mode 100644 lib/msqr.py create mode 100644 lib/pyqrnative.py create mode 100644 lib/ripemd.py create mode 100644 lib/version.py create mode 100644 lib/wallet.py delete mode 100644 mnemonic.py delete mode 100644 msqr.py delete mode 100644 pyqrnative.py delete mode 100644 ripemd.py delete mode 100644 version.py delete mode 100644 wallet.py diff --git a/MANIFEST.in b/MANIFEST.in index 6962801..5ff6c0b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include README LICENCE RELEASE-NOTES include *.py include electrum exclude setup.py +recursive-include lib *.py recursive-include ecdsa *.py recursive-include aes *.py include icons.qrc diff --git a/README b/README index a90857e..a1567d2 100644 --- a/README +++ b/README @@ -30,17 +30,22 @@ In order to use the gtk gui, you need pygtk and tk. * apt-get install python-tk +To install Electrum, type: + + sudo python setup.py install + + RUN -To start the Qt gui, type: - python electrum -To use the Gtk gui, type: - python electrum --gui gtk +To start Electrum in GUI mode, type: + + electrum + + +If arguments are passed to the command line, Electrum will run in text mode: -If arguments are passed to the command line, Electrum will run in text mode. -Examples: - python electrum balance - python electrum help + electrum balance + electrum help diff --git a/blocks b/blocks index baf4548..cf03340 100755 --- a/blocks +++ b/blocks @@ -1,8 +1,8 @@ #!/usr/bin/env python -import interface +from electrum import TcpStratumInterface -i = interface.TcpStratumInterface('ecdsa.org', 50001) +i = TcpStratumInterface('ecdsa.org', 50001) i.start() i.send([('blockchain.numblocks.subscribe',[])]) diff --git a/bmp.py b/bmp.py deleted file mode 100644 index b4bd410..0000000 --- a/bmp.py +++ /dev/null @@ -1,222 +0,0 @@ -# -*- coding: utf-8 -*- -""" -bmp.py - module for constructing simple BMP graphics files - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -""" -__version__ = "0.3" -__about = "bmp module, version %s, written by Paul McGuire, October, 2003, updated by Margus Laak, September, 2009" % __version__ - -from math import ceil, hypot - - -def shortToString(i): - hi = (i & 0xff00) >> 8 - lo = i & 0x00ff - return chr(lo) + chr(hi) - -def longToString(i): - hi = (long(i) & 0x7fff0000) >> 16 - lo = long(i) & 0x0000ffff - return shortToString(lo) + shortToString(hi) - -def long24ToString(i): - return chr(i & 0xff) + chr(i >> 8 & 0xff) + chr(i >> 16 & 0xff) - -def stringToLong(input_string, offset): - return ord(input_string[offset+3]) << 24 | ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset]) - -def stringToLong24(input_string, offset): - return ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset]) - -class Color(object): - """class for specifying colors while drawing BitMap elements""" - __slots__ = [ 'red', 'grn', 'blu' ] - __shade = 32 - - def __init__( self, r=0, g=0, b=0 ): - self.red = r - self.grn = g - self.blu = b - - def __setattr__(self, name, value): - if hasattr(self, name): - raise AttributeError, "Color is immutable" - else: - object.__setattr__(self, name, value) - - def __str__( self ): - return "R:%d G:%d B:%d" % (self.red, self.grn, self.blu ) - - def __hash__( self ): - return ( ( long(self.blu) ) + - ( long(self.grn) << 8 ) + - ( long(self.red) << 16 ) ) - - def __eq__( self, other ): - return (self is other) or (self.toLong == other.toLong) - - def lighten( self ): - return Color( - min( self.red + Color.__shade, 255), - min( self.grn + Color.__shade, 255), - min( self.blu + Color.__shade, 255) - ) - - def darken( self ): - return Color( - max( self.red - Color.__shade, 0), - max( self.grn - Color.__shade, 0), - max( self.blu - Color.__shade, 0) - ) - - def toLong( self ): - return self.__hash__() - - def fromLong( l ): - b = l & 0xff - l = l >> 8 - g = l & 0xff - l = l >> 8 - r = l & 0xff - return Color( r, g, b ) - fromLong = staticmethod(fromLong) - -# define class constants for common colors -Color.BLACK = Color( 0, 0, 0 ) -Color.RED = Color( 255, 0, 0 ) -Color.GREEN = Color( 0, 255, 0 ) -Color.BLUE = Color( 0, 0, 255 ) -Color.CYAN = Color( 0, 255, 255 ) -Color.MAGENTA = Color( 255, 0, 255 ) -Color.YELLOW = Color( 255, 255, 0 ) -Color.WHITE = Color( 255, 255, 255 ) -Color.DKRED = Color( 128, 0, 0 ) -Color.DKGREEN = Color( 0, 128, 0 ) -Color.DKBLUE = Color( 0, 0, 128 ) -Color.TEAL = Color( 0, 128, 128 ) -Color.PURPLE = Color( 128, 0, 128 ) -Color.BROWN = Color( 128, 128, 0 ) -Color.GRAY = Color( 128, 128, 128 ) - - -class BitMap(object): - """class for drawing and saving simple Windows bitmap files""" - - LINE_SOLID = 0 - LINE_DASHED = 1 - LINE_DOTTED = 2 - LINE_DOT_DASH=3 - _DASH_LEN = 12.0 - _DOT_LEN = 6.0 - _DOT_DASH_LEN = _DOT_LEN + _DASH_LEN - - def __init__( self, width, height, - bkgd = Color.WHITE, frgd = Color.BLACK ): - self.wd = int( ceil(width) ) - self.ht = int( ceil(height) ) - self.bgcolor = 0 - self.fgcolor = 1 - self.palette = [] - self.palette.append( bkgd.toLong() ) - self.palette.append( frgd.toLong() ) - self.currentPen = self.fgcolor - - tmparray = [ self.bgcolor ] * self.wd - self.bitarray = [ tmparray[:] for i in range( self.ht ) ] - self.currentPen = 1 - - - def plotPoint( self, x, y ): - if ( 0 <= x < self.wd and 0 <= y < self.ht ): - x = int(x) - y = int(y) - self.bitarray[y][x] = self.currentPen - - - def _saveBitMapNoCompression( self ): - line_padding = (4 - (self.wd % 4)) % 4 - - # write bitmap header - _bitmap = "BM" - _bitmap += longToString( 54 + self.ht*(self.wd*3 + line_padding) ) # DWORD size in bytes of the file - _bitmap += longToString( 0 ) # DWORD 0 - _bitmap += longToString( 54 ) - _bitmap += longToString( 40 ) # DWORD header size = 40 - _bitmap += longToString( self.wd ) # DWORD image width - _bitmap += longToString( self.ht ) # DWORD image height - _bitmap += shortToString( 1 ) # WORD planes = 1 - _bitmap += shortToString( 24 ) # WORD bits per pixel = 8 - _bitmap += longToString( 0 ) # DWORD compression = 0 - _bitmap += longToString( self.ht * (self.wd * 3 + line_padding) ) # DWORD sizeimage = size in bytes of the bitmap = width * height - _bitmap += longToString( 0 ) # DWORD horiz pixels per meter (?) - _bitmap += longToString( 0 ) # DWORD ver pixels per meter (?) - _bitmap += longToString( 0 ) # DWORD number of colors used = 256 - _bitmap += longToString( 0 ) # DWORD number of "import colors = len( self.palette ) - - # write pixels - self.bitarray.reverse() - for row in self.bitarray: - for pixel in row: - c = self.palette[pixel] - _bitmap += long24ToString(c) - for i in range(line_padding): - _bitmap += chr( 0 ) - - return _bitmap - - - - def saveFile( self, filename): - _b = self._saveBitMapNoCompression( ) - - f = file(filename, 'wb') - f.write(_b) - f.close() - - -def save_qrcode(qr, filename): - bitmap = BitMap( 35*8, 35*8 ) - #print len(bitmap.bitarray) - bitmap.bitarray = [] - k = 33 - for r in range(35): - tmparray = [ 0 ] * 35*8 - - if 0 < r < 34: - for c in range(k): - if qr.isDark(r-1, c): - tmparray[ (1+c)*8:(2+c)*8] = [1]*8 - - for i in range(8): - bitmap.bitarray.append( tmparray[:] ) - - bitmap.saveFile( filename ) - - - -if __name__ == "__main__": - - bmp = BitMap( 10, 10 ) - bmp.plotPoint( 5, 5 ) - bmp.plotPoint( 0, 0 ) - bmp.saveFile( "test.bmp" ) - diff --git a/electrum b/electrum index e7f378d..cd93755 100755 --- a/electrum +++ b/electrum @@ -18,11 +18,11 @@ import re, sys, getpass +import electrum from optparse import OptionParser -from wallet import Wallet, SecretToASecret -from interface import WalletSynchronizer from decimal import Decimal -from wallet import format_satoshis + +from electrum import Wallet, SecretToASecret, WalletSynchronizer, format_satoshis known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval'] offline_commands = ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed'] @@ -60,9 +60,9 @@ if __name__ == '__main__': if cmd == 'gui': if options.gui=='gtk': - import gui + import electrum.gui as gui elif options.gui=='qt': - import gui_qt as gui + import electrum.gui_qt as gui else: print "unknown gui", options.gui exit(1) diff --git a/gui.py b/gui.py deleted file mode 100644 index 3b7928f..0000000 --- a/gui.py +++ /dev/null @@ -1,1297 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2011 thomasv@gitorious -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import datetime -import thread, time, ast, sys, re -import socket, traceback -import pygtk -pygtk.require('2.0') -import gtk, gobject -import pyqrnative -from decimal import Decimal - -gtk.gdk.threads_init() -APP_NAME = "Electrum" -import platform -MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace' - -from wallet import format_satoshis -from interface import DEFAULT_SERVERS - -def numbify(entry, is_int = False): - text = entry.get_text().strip() - chars = '0123456789' - if not is_int: chars +='.' - s = ''.join([i for i in text if i in chars]) - if not is_int: - if '.' in s: - p = s.find('.') - s = s.replace('.','') - s = s[:p] + '.' + s[p:p+8] - try: - amount = int( Decimal(s) * 100000000 ) - except: - amount = None - else: - try: - amount = int( s ) - except: - amount = None - entry.set_text(s) - return amount - - - - -def show_seed_dialog(wallet, password, parent): - import mnemonic - try: - seed = wallet.pw_decode( wallet.seed, password) - except: - show_message("Incorrect password") - return - dialog = gtk.MessageDialog( - parent = parent, - flags = gtk.DIALOG_MODAL, - buttons = gtk.BUTTONS_OK, - message_format = "Your wallet generation seed is:\n\n" + seed \ - + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \ - + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" ) - dialog.set_title("Seed") - dialog.show() - dialog.run() - dialog.destroy() - -def restore_create_dialog(wallet): - - # ask if the user wants to create a new wallet, or recover from a seed. - # if he wants to recover, and nothing is found, do not create wallet - dialog = gtk.Dialog("electrum", parent=None, - flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, - buttons= ("create", 0, "restore",1, "cancel",2) ) - - label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?" ) - label.show() - dialog.vbox.pack_start(label) - dialog.show() - r = dialog.run() - dialog.destroy() - - if r==2: return False - - is_recovery = (r==1) - - # ask for the server. - if not run_network_dialog( wallet, parent=None ): return False - - if not is_recovery: - - wallet.new_seed(None) - # generate first key - wallet.init_mpk( wallet.seed ) - wallet.up_to_date_event.clear() - wallet.update() - - # run a dialog indicating the seed, ask the user to remember it - show_seed_dialog(wallet, None, None) - - #ask for password - change_password_dialog(wallet, None, None) - else: - # ask for seed and gap. - run_recovery_dialog( wallet ) - - dialog = gtk.MessageDialog( - parent = None, - flags = gtk.DIALOG_MODAL, - buttons = gtk.BUTTONS_CANCEL, - message_format = "Please wait..." ) - dialog.show() - - def recover_thread( wallet, dialog ): - wallet.init_mpk( wallet.seed ) # not encrypted at this point - wallet.up_to_date_event.clear() - wallet.update() - - if wallet.is_found(): - # history and addressbook - wallet.update_tx_history() - wallet.fill_addressbook() - print "recovery successful" - - gobject.idle_add( dialog.destroy ) - - thread.start_new_thread( recover_thread, ( wallet, dialog ) ) - r = dialog.run() - dialog.destroy() - if r==gtk.RESPONSE_CANCEL: return False - if not wallet.is_found: - show_message("No transactions found for this seed") - - wallet.save() - return True - - -def run_recovery_dialog(wallet): - message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet." - dialog = gtk.MessageDialog( - parent = None, - flags = gtk.DIALOG_MODAL, - buttons = gtk.BUTTONS_OK_CANCEL, - message_format = message) - - vbox = dialog.vbox - dialog.set_default_response(gtk.RESPONSE_OK) - - # ask seed, server and gap in the same dialog - seed_box = gtk.HBox() - seed_label = gtk.Label('Seed or mnemonic:') - seed_label.set_size_request(150,-1) - seed_box.pack_start(seed_label, False, False, 10) - seed_label.show() - seed_entry = gtk.Entry() - seed_entry.show() - seed_entry.set_size_request(450,-1) - seed_box.pack_start(seed_entry, False, False, 10) - add_help_button(seed_box, '.') - seed_box.show() - vbox.pack_start(seed_box, False, False, 5) - - gap = gtk.HBox() - gap_label = gtk.Label('Gap limit:') - gap_label.set_size_request(150,10) - gap_label.show() - gap.pack_start(gap_label,False, False, 10) - gap_entry = gtk.Entry() - gap_entry.set_text("%d"%wallet.gap_limit) - gap_entry.connect('changed', numbify, True) - gap_entry.show() - gap.pack_start(gap_entry,False,False, 10) - add_help_button(gap, 'The maximum gap that is allowed between unused addresses in your wallet. During wallet recovery, this parameter is used to decide when to stop the recovery process. If you increase this value, you will need to remember it in order to be able to recover your wallet from seed.') - gap.show() - vbox.pack_start(gap, False,False, 5) - - dialog.show() - r = dialog.run() - gap = gap_entry.get_text() - seed = seed_entry.get_text() - dialog.destroy() - - if r==gtk.RESPONSE_CANCEL: - sys.exit(1) - try: - gap = int(gap) - except: - show_message("error") - sys.exit(1) - - try: - seed.decode('hex') - except: - import mnemonic - print "not hex, trying decode" - seed = mnemonic.mn_decode( seed.split(' ') ) - if not seed: - show_message("no seed") - sys.exit(1) - - wallet.seed = seed - wallet.gap_limit = gap - wallet.save() - - - -def run_settings_dialog(wallet, parent): - - message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field." - - dialog = gtk.MessageDialog( - parent = parent, - flags = gtk.DIALOG_MODAL, - buttons = gtk.BUTTONS_OK_CANCEL, - message_format = message) - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG) - image.show() - dialog.set_image(image) - dialog.set_title("Settings") - - vbox = dialog.vbox - dialog.set_default_response(gtk.RESPONSE_OK) - - fee = gtk.HBox() - fee_entry = gtk.Entry() - fee_label = gtk.Label('Transaction fee:') - fee_label.set_size_request(150,10) - fee_label.show() - fee.pack_start(fee_label,False, False, 10) - fee_entry.set_text( str( Decimal(wallet.fee) /100000000 ) ) - fee_entry.connect('changed', numbify, False) - fee_entry.show() - fee.pack_start(fee_entry,False,False, 10) - add_help_button(fee, 'Fee per transaction input. Transactions involving multiple inputs tend to have a higher fee. Recommended value:0.0005') - fee.show() - vbox.pack_start(fee, False,False, 5) - - nz = gtk.HBox() - nz_entry = gtk.Entry() - nz_label = gtk.Label('Display zeros:') - nz_label.set_size_request(150,10) - nz_label.show() - nz.pack_start(nz_label,False, False, 10) - nz_entry.set_text( str( wallet.num_zeros )) - nz_entry.connect('changed', numbify, True) - nz_entry.show() - nz.pack_start(nz_entry,False,False, 10) - add_help_button(nz, "Number of zeros displayed after the decimal point.\nFor example, if this number is 2, then '5.' is displayed as '5.00'") - nz.show() - vbox.pack_start(nz, False,False, 5) - - dialog.show() - r = dialog.run() - fee = fee_entry.get_text() - nz = nz_entry.get_text() - - dialog.destroy() - if r==gtk.RESPONSE_CANCEL: - return - - try: - fee = int( 100000000 * Decimal(fee) ) - except: - show_message("error") - return - if wallet.fee != fee: - wallet.fee = fee - wallet.save() - - try: - nz = int( nz ) - if nz>8: nz = 8 - except: - show_message("error") - return - if wallet.num_zeros != nz: - wallet.num_zeros = nz - wallet.save() - - - - -def run_network_dialog( wallet, parent ): - image = gtk.Image() - image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG) - interface = wallet.interface - if parent: - if interface.is_connected: - status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks) - else: - status = "Not connected" - server = wallet.server - else: - import random - status = "Please choose a server." - server = random.choice( DEFAULT_SERVERS ) - - if not wallet.interface.servers: - servers_list = [] - for x in DEFAULT_SERVERS: - h,port,protocol = x.split(':') - servers_list.append( (h,[(protocol,port)] ) ) - else: - servers_list = wallet.interface.servers - - plist = {} - for item in servers_list: - host, pp = item - z = {} - for item2 in pp: - protocol, port = item2 - z[protocol] = port - plist[host] = z - - dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status) - dialog.set_title("Server") - dialog.set_image(image) - image.show() - - vbox = dialog.vbox - host_box = gtk.HBox() - host_label = gtk.Label('Connect to:') - host_label.set_size_request(100,-1) - host_label.show() - host_box.pack_start(host_label, False, False, 10) - host_entry = gtk.Entry() - host_entry.set_size_request(200,-1) - host_entry.set_text(server) - host_entry.show() - host_box.pack_start(host_entry, False, False, 10) - add_help_button(host_box, 'The name and port number of your Electrum server, separated by a colon. Example: "ecdsa.org:50000". If no port number is provided, port 50000 will be tried. Some servers allow you to connect through http (port 80) or https (port 443)') - host_box.show() - - - p_box = gtk.HBox(False, 10) - p_box.show() - - p_label = gtk.Label('Protocol:') - p_label.set_size_request(100,-1) - p_label.show() - p_box.pack_start(p_label, False, False, 10) - - radio1 = gtk.RadioButton(None, "tcp") - p_box.pack_start(radio1, True, True, 0) - radio1.show() - radio2 = gtk.RadioButton(radio1, "http") - p_box.pack_start(radio2, True, True, 0) - radio2.show() - - def current_line(): - return unicode(host_entry.get_text()).split(':') - - def set_button(protocol): - if protocol == 't': - radio1.set_active(1) - elif protocol == 'h': - radio2.set_active(1) - - def set_protocol(protocol): - host = current_line()[0] - pp = plist[host] - if protocol not in pp.keys(): - protocol = pp.keys()[0] - set_button(protocol) - port = pp[protocol] - host_entry.set_text( host + ':' + port + ':' + protocol) - - radio1.connect("toggled", lambda x,y:set_protocol('t'), "radio button 1") - radio2.connect("toggled", lambda x,y:set_protocol('h'), "radio button 1") - - server_list = gtk.ListStore(str) - for host in plist.keys(): - server_list.append([host]) - - treeview = gtk.TreeView(model=server_list) - treeview.show() - - if wallet.interface.servers: - label = 'Active Servers' - else: - label = 'Default Servers' - - tvcolumn = gtk.TreeViewColumn(label) - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - tvcolumn.pack_start(cell, False) - tvcolumn.add_attribute(cell, 'text', 0) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scroll.add(treeview) - scroll.show() - - vbox.pack_start(host_box, False,False, 5) - vbox.pack_start(p_box, True, True, 0) - vbox.pack_start(scroll) - - def my_treeview_cb(treeview): - path, view_column = treeview.get_cursor() - host = server_list.get_value( server_list.get_iter(path), 0) - - pp = plist[host] - if 't' in pp.keys(): - protocol = 't' - else: - protocol = pp.keys()[0] - port = pp[protocol] - host_entry.set_text( host + ':' + port + ':' + protocol) - set_button(protocol) - - treeview.connect('cursor-changed', my_treeview_cb) - - dialog.show() - r = dialog.run() - server = host_entry.get_text() - dialog.destroy() - - if r==gtk.RESPONSE_CANCEL: - return False - - try: - wallet.set_server(server) - except: - show_message("error:" + server) - return False - - if parent: - wallet.save() - return True - - - -def show_message(message, parent=None): - dialog = gtk.MessageDialog( - parent = parent, - flags = gtk.DIALOG_MODAL, - buttons = gtk.BUTTONS_CLOSE, - message_format = message ) - dialog.show() - dialog.run() - dialog.destroy() - -def password_line(label): - password = gtk.HBox() - password_label = gtk.Label(label) - password_label.set_size_request(120,10) - password_label.show() - password.pack_start(password_label,False, False, 10) - password_entry = gtk.Entry() - password_entry.set_size_request(300,-1) - password_entry.set_visibility(False) - password_entry.show() - password.pack_start(password_entry,False,False, 10) - password.show() - return password, password_entry - -def password_dialog(parent): - dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.") - dialog.get_image().set_visible(False) - current_pw, current_pw_entry = password_line('Password:') - current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK) - dialog.vbox.pack_start(current_pw, False, True, 0) - dialog.show() - result = dialog.run() - pw = current_pw_entry.get_text() - dialog.destroy() - if result != gtk.RESPONSE_CANCEL: return pw - -def change_password_dialog(wallet, parent, icon): - if parent: - msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' - else: - msg = "Please choose a password to encrypt your wallet keys" - - dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg) - dialog.set_title("Change password") - image = gtk.Image() - image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG) - image.show() - dialog.set_image(image) - - if wallet.use_encryption: - current_pw, current_pw_entry = password_line('Current password:') - dialog.vbox.pack_start(current_pw, False, True, 0) - - password, password_entry = password_line('New password:') - dialog.vbox.pack_start(password, False, True, 5) - password2, password2_entry = password_line('Confirm password:') - dialog.vbox.pack_start(password2, False, True, 5) - - dialog.show() - result = dialog.run() - password = current_pw_entry.get_text() if wallet.use_encryption else None - new_password = password_entry.get_text() - new_password2 = password2_entry.get_text() - dialog.destroy() - if result == gtk.RESPONSE_CANCEL: - return - - try: - seed = wallet.pw_decode( wallet.seed, password) - except: - show_message("Incorrect password") - return - - if new_password != new_password2: - show_message("passwords do not match") - return - - wallet.update_password(seed, new_password) - - if icon: - if wallet.use_encryption: - icon.set_tooltip_text('wallet is encrypted') - else: - icon.set_tooltip_text('wallet is unencrypted') - - -def add_help_button(hbox, message): - button = gtk.Button('?') - button.connect("clicked", lambda x: show_message(message)) - button.show() - hbox.pack_start(button,False, False) - - -class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) ) - -gobject.type_register(MyWindow) -gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W') -gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q') - - -class ElectrumWindow: - - def show_message(self, msg): - show_message(msg, self.window) - - def __init__(self, wallet): - self.wallet = wallet - self.funds_error = False # True if not enough funds - - self.window = MyWindow(gtk.WINDOW_TOPLEVEL) - self.window.set_title(APP_NAME + " " + self.wallet.electrum_version) - self.window.connect("destroy", gtk.main_quit) - self.window.set_border_width(0) - self.window.connect('mykeypress', gtk.main_quit) - self.window.set_default_size(720, 350) - - vbox = gtk.VBox() - - self.notebook = gtk.Notebook() - self.create_history_tab() - self.create_send_tab() - self.create_recv_tab() - self.create_book_tab() - self.create_about_tab() - self.notebook.show() - vbox.pack_start(self.notebook, True, True, 2) - - self.status_bar = gtk.Statusbar() - vbox.pack_start(self.status_bar, False, False, 0) - - self.status_image = gtk.Image() - self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) - self.status_image.set_alignment(True, 0.5 ) - self.status_image.show() - - self.network_button = gtk.Button() - self.network_button.connect("clicked", lambda x: run_network_dialog(self.wallet, self.window) ) - self.network_button.add(self.status_image) - self.network_button.set_relief(gtk.RELIEF_NONE) - self.network_button.show() - self.status_bar.pack_end(self.network_button, False, False) - - def seedb(w, wallet): - if wallet.use_encryption: - password = password_dialog(self.window) - if not password: return - else: password = None - show_seed_dialog(wallet, password, self.window) - button = gtk.Button('S') - button.connect("clicked", seedb, wallet ) - button.set_relief(gtk.RELIEF_NONE) - button.show() - self.status_bar.pack_end(button,False, False) - - settings_icon = gtk.Image() - settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - settings_icon.set_alignment(0.5, 0.5) - settings_icon.set_size_request(16,16 ) - settings_icon.show() - - prefs_button = gtk.Button() - prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) ) - prefs_button.add(settings_icon) - prefs_button.set_tooltip_text("Settings") - prefs_button.set_relief(gtk.RELIEF_NONE) - prefs_button.show() - self.status_bar.pack_end(prefs_button,False,False) - - pw_icon = gtk.Image() - pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) - pw_icon.set_alignment(0.5, 0.5) - pw_icon.set_size_request(16,16 ) - pw_icon.show() - - password_button = gtk.Button() - password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) - password_button.add(pw_icon) - password_button.set_relief(gtk.RELIEF_NONE) - password_button.show() - self.status_bar.pack_end(password_button,False,False) - - self.window.add(vbox) - self.window.show_all() - #self.fee_box.hide() - - self.context_id = self.status_bar.get_context_id("statusbar") - self.update_status_bar() - - def update_status_bar_thread(): - while True: - gobject.idle_add( self.update_status_bar ) - time.sleep(0.5) - - - def check_recipient_thread(): - old_r = '' - while True: - time.sleep(0.5) - if self.payto_entry.is_focus(): - continue - r = self.payto_entry.get_text() - if r != old_r: - old_r = r - r = r.strip() - if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r): - try: - to_address = self.wallet.get_alias(r, interactive=False) - except: - continue - if to_address: - s = r + ' <' + to_address + '>' - gobject.idle_add( lambda: self.payto_entry.set_text(s) ) - - - thread.start_new_thread(update_status_bar_thread, ()) - thread.start_new_thread(check_recipient_thread, ()) - self.notebook.set_current_page(0) - - - def add_tab(self, page, name): - tab_label = gtk.Label(name) - tab_label.show() - self.notebook.append_page(page, tab_label) - - - def create_send_tab(self): - - page = vbox = gtk.VBox() - page.show() - - payto = gtk.HBox() - payto_label = gtk.Label('Pay to:') - payto_label.set_size_request(100,-1) - payto.pack_start(payto_label, False) - payto_entry = gtk.Entry() - payto_entry.set_size_request(450, 26) - payto.pack_start(payto_entry, False) - vbox.pack_start(payto, False, False, 5) - - message = gtk.HBox() - message_label = gtk.Label('Description:') - message_label.set_size_request(100,-1) - message.pack_start(message_label, False) - message_entry = gtk.Entry() - message_entry.set_size_request(450, 26) - message.pack_start(message_entry, False) - vbox.pack_start(message, False, False, 5) - - amount_box = gtk.HBox() - amount_label = gtk.Label('Amount:') - amount_label.set_size_request(100,-1) - amount_box.pack_start(amount_label, False) - amount_entry = gtk.Entry() - amount_entry.set_size_request(120, -1) - amount_box.pack_start(amount_entry, False) - vbox.pack_start(amount_box, False, False, 5) - - self.fee_box = fee_box = gtk.HBox() - fee_label = gtk.Label('Fee:') - fee_label.set_size_request(100,-1) - fee_box.pack_start(fee_label, False) - fee_entry = gtk.Entry() - fee_entry.set_size_request(60, 26) - fee_box.pack_start(fee_entry, False) - vbox.pack_start(fee_box, False, False, 5) - - end_box = gtk.HBox() - empty_label = gtk.Label('') - empty_label.set_size_request(100,-1) - end_box.pack_start(empty_label, False) - send_button = gtk.Button("Send") - send_button.show() - end_box.pack_start(send_button, False, False, 0) - clear_button = gtk.Button("Clear") - clear_button.show() - end_box.pack_start(clear_button, False, False, 15) - send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry)) - clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry)) - - vbox.pack_start(end_box, False, False, 5) - - # display this line only if there is a signature - payto_sig = gtk.HBox() - payto_sig_id = gtk.Label('') - payto_sig.pack_start(payto_sig_id, False) - vbox.pack_start(payto_sig, True, True, 5) - - - self.user_fee = False - - def entry_changed( entry, is_fee ): - self.funds_error = False - amount = numbify(amount_entry) - fee = numbify(fee_entry) - if not is_fee: fee = None - if amount is None: - return - inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee ) - if not is_fee: - fee_entry.set_text( str( Decimal( fee ) / 100000000 ) ) - self.fee_box.show() - if inputs: - amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000")) - fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000")) - send_button.set_sensitive(True) - else: - send_button.set_sensitive(False) - amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000")) - fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000")) - self.funds_error = True - - amount_entry.connect('changed', entry_changed, False) - fee_entry.connect('changed', entry_changed, True) - - self.payto_entry = payto_entry - self.payto_fee_entry = fee_entry - self.payto_sig_id = payto_sig_id - self.payto_sig = payto_sig - self.amount_entry = amount_entry - self.message_entry = message_entry - self.add_tab(page, 'Send') - - def set_frozen(self,entry,frozen): - if frozen: - entry.set_editable(False) - entry.set_has_frame(False) - entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee")) - else: - entry.set_editable(True) - entry.set_has_frame(True) - entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff")) - - def set_url(self, url): - payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question) - self.notebook.set_current_page(1) - self.payto_entry.set_text(payto) - self.message_entry.set_text(message) - self.amount_entry.set_text(amount) - if identity: - self.set_frozen(self.payto_entry,True) - self.set_frozen(self.amount_entry,True) - self.set_frozen(self.message_entry,True) - self.payto_sig_id.set_text( ' The bitcoin URI was signed by ' + identity ) - else: - self.payto_sig.set_visible(False) - - def create_about_tab(self): - import pango - page = gtk.VBox() - page.show() - tv = gtk.TextView() - tv.set_editable(False) - tv.set_cursor_visible(False) - tv.modify_font(pango.FontDescription(MONOSPACE_FONT)) - page.pack_start(tv) - self.info = tv.get_buffer() - self.add_tab(page, 'Wall') - - def do_clear(self, w, data): - self.payto_sig.set_visible(False) - self.payto_fee_entry.set_text('') - for entry in [self.payto_entry,self.amount_entry,self.message_entry]: - self.set_frozen(entry,False) - entry.set_text('') - - def question(self,msg): - dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg) - dialog.show() - result = dialog.run() - dialog.destroy() - return result == gtk.RESPONSE_OK - - def do_send(self, w, data): - payto_entry, label_entry, amount_entry, fee_entry = data - label = label_entry.get_text() - r = payto_entry.get_text() - r = r.strip() - - m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r) - m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) - - if m1: - to_address = self.wallet.get_alias(r, True, self.show_message, self.question) - if not to_address: - return - else: - self.update_sending_tab() - - elif m2: - to_address = m2.group(5) - else: - to_address = r - - if not self.wallet.is_valid(to_address): - self.show_message( "invalid bitcoin address:\n"+to_address) - return - - try: - amount = int( Decimal(amount_entry.get_text()) * 100000000 ) - except: - self.show_message( "invalid amount") - return - try: - fee = int( Decimal(fee_entry.get_text()) * 100000000 ) - except: - self.show_message( "invalid fee") - return - - if self.wallet.use_encryption: - password = password_dialog(self.window) - if not password: - return - else: - password = None - - try: - tx = self.wallet.mktx( to_address, amount, label, password, fee ) - except BaseException, e: - self.show_message(e.message) - return - - status, msg = self.wallet.sendtx( tx ) - if status: - self.show_message( "payment sent.\n" + msg ) - payto_entry.set_text("") - label_entry.set_text("") - amount_entry.set_text("") - fee_entry.set_text("") - #self.fee_box.hide() - self.update_sending_tab() - else: - self.show_message( msg ) - - - def treeview_button_press(self, treeview, event): - if event.type == gtk.gdk._2BUTTON_PRESS: - c = treeview.get_cursor()[0] - if treeview == self.history_treeview: - tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) - self.show_message(tx_details) - elif treeview == self.contacts_treeview: - m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) - a = self.wallet.aliases.get(m) - if a: - if a[0] in self.wallet.authorities.keys(): - s = self.wallet.authorities.get(a[0]) - else: - s = "self-signed" - msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] - self.show_message(msg) - - - def treeview_key_press(self, treeview, event): - c = treeview.get_cursor()[0] - if event.keyval == gtk.keysyms.Up: - if c and c[0] == 0: - treeview.parent.grab_focus() - treeview.set_cursor((0,)) - elif event.keyval == gtk.keysyms.Return: - if treeview == self.history_treeview: - tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) - self.show_message(tx_details) - elif treeview == self.contacts_treeview: - m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) - a = self.wallet.aliases.get(m) - if a: - if a[0] in self.wallet.authorities.keys(): - s = self.wallet.authorities.get(a[0]) - else: - s = "self" - msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0] - self.show_message(msg) - - return False - - def create_history_tab(self): - - self.history_list = gtk.ListStore(str, str, str, str, 'gboolean', str, str, str, str) - treeview = gtk.TreeView(model=self.history_list) - self.history_treeview = treeview - treeview.set_tooltip_column(7) - treeview.show() - treeview.connect('key-press-event', self.treeview_key_press) - treeview.connect('button-press-event', self.treeview_button_press) - - tvcolumn = gtk.TreeViewColumn('') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererPixbuf() - tvcolumn.pack_start(cell, False) - tvcolumn.set_attributes(cell, stock_id=1) - - tvcolumn = gtk.TreeViewColumn('Date') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - tvcolumn.pack_start(cell, False) - tvcolumn.add_attribute(cell, 'text', 2) - - tvcolumn = gtk.TreeViewColumn('Description') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - cell.set_property('foreground', 'grey') - cell.set_property('family', MONOSPACE_FONT) - cell.set_property('editable', True) - def edited_cb(cell, path, new_text, h_list): - tx = h_list.get_value( h_list.get_iter(path), 0) - self.wallet.labels[tx] = new_text - self.wallet.save() - self.update_history_tab() - cell.connect('edited', edited_cb, self.history_list) - def editing_started(cell, entry, path, h_list): - tx = h_list.get_value( h_list.get_iter(path), 0) - if not self.wallet.labels.get(tx): entry.set_text('') - cell.connect('editing-started', editing_started, self.history_list) - tvcolumn.set_expand(True) - tvcolumn.pack_start(cell, True) - tvcolumn.set_attributes(cell, text=3, foreground_set = 4) - - tvcolumn = gtk.TreeViewColumn('Amount') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - cell.set_alignment(1, 0.5) - cell.set_property('family', MONOSPACE_FONT) - tvcolumn.pack_start(cell, False) - tvcolumn.add_attribute(cell, 'text', 5) - - tvcolumn = gtk.TreeViewColumn('Balance') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - cell.set_alignment(1, 0.5) - cell.set_property('family', MONOSPACE_FONT) - tvcolumn.pack_start(cell, False) - tvcolumn.add_attribute(cell, 'text', 6) - - tvcolumn = gtk.TreeViewColumn('Tooltip') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - tvcolumn.pack_start(cell, False) - tvcolumn.add_attribute(cell, 'text', 7) - tvcolumn.set_visible(False) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - scroll.add(treeview) - - self.add_tab(scroll, 'History') - self.update_history_tab() - - - def create_recv_tab(self): - self.recv_list = gtk.ListStore(str, str, str) - self.add_tab( self.make_address_list(True), 'Receive') - self.update_receiving_tab() - - def create_book_tab(self): - self.addressbook_list = gtk.ListStore(str, str, str) - self.add_tab( self.make_address_list(False), 'Contacts') - self.update_sending_tab() - - def make_address_list(self, is_recv): - liststore = self.recv_list if is_recv else self.addressbook_list - treeview = gtk.TreeView(model= liststore) - treeview.connect('key-press-event', self.treeview_key_press) - treeview.connect('button-press-event', self.treeview_button_press) - treeview.show() - if not is_recv: - self.contacts_treeview = treeview - - tvcolumn = gtk.TreeViewColumn('Address') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - cell.set_property('family', MONOSPACE_FONT) - tvcolumn.pack_start(cell, True) - tvcolumn.add_attribute(cell, 'text', 0) - - tvcolumn = gtk.TreeViewColumn('Label') - tvcolumn.set_expand(True) - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - cell.set_property('editable', True) - def edited_cb2(cell, path, new_text, liststore): - address = liststore.get_value( liststore.get_iter(path), 0) - self.wallet.labels[address] = new_text - self.wallet.save() - self.wallet.update_tx_labels() - self.update_receiving_tab() - self.update_sending_tab() - self.update_history_tab() - cell.connect('edited', edited_cb2, liststore) - tvcolumn.pack_start(cell, True) - tvcolumn.add_attribute(cell, 'text', 1) - - tvcolumn = gtk.TreeViewColumn('Tx') - treeview.append_column(tvcolumn) - cell = gtk.CellRendererText() - tvcolumn.pack_start(cell, True) - tvcolumn.add_attribute(cell, 'text', 2) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scroll.add(treeview) - - hbox = gtk.HBox() - if not is_recv: - button = gtk.Button("New") - button.connect("clicked", self.newaddress_dialog) - button.show() - hbox.pack_start(button,False) - - def showqrcode(w, treeview, liststore): - path, col = treeview.get_cursor() - if not path: return - address = liststore.get_value(liststore.get_iter(path), 0) - qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H) - qr.addData(address) - qr.make() - boxsize = 7 - size = qr.getModuleCount()*boxsize - def area_expose_cb(area, event): - style = area.get_style() - k = qr.getModuleCount() - for r in range(k): - for c in range(k): - gc = style.black_gc if qr.isDark(r, c) else style.white_gc - area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize) - area = gtk.DrawingArea() - area.set_size_request(size, size) - area.connect("expose-event", area_expose_cb) - area.show() - dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1)) - dialog.vbox.add(area) - dialog.run() - dialog.destroy() - - button = gtk.Button("QR") - button.connect("clicked", showqrcode, treeview, liststore) - button.show() - hbox.pack_start(button,False) - - button = gtk.Button("Copy to clipboard") - def copy2clipboard(w, treeview, liststore): - import platform - path, col = treeview.get_cursor() - if path: - address = liststore.get_value( liststore.get_iter(path), 0) - if platform.system() == 'Windows': - from Tkinter import Tk - r = Tk() - r.withdraw() - r.clipboard_clear() - r.clipboard_append( address ) - r.destroy() - else: - c = gtk.clipboard_get() - c.set_text( address ) - button.connect("clicked", copy2clipboard, treeview, liststore) - button.show() - hbox.pack_start(button,False) - - if not is_recv: - button = gtk.Button("Pay to") - def payto(w, treeview, liststore): - path, col = treeview.get_cursor() - if path: - address = liststore.get_value( liststore.get_iter(path), 0) - self.payto_entry.set_text( address ) - self.notebook.set_current_page(1) - self.amount_entry.grab_focus() - - button.connect("clicked", payto, treeview, liststore) - button.show() - hbox.pack_start(button,False) - - vbox = gtk.VBox() - vbox.pack_start(scroll,True) - vbox.pack_start(hbox, False) - return vbox - - def update_status_bar(self): - interface = self.wallet.interface - if self.funds_error: - text = "Not enough funds" - elif interface.is_connected: - self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks)) - if self.wallet.blocks == -1: - self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) - text = "Connecting..." - elif self.wallet.blocks == 0: - self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) - text = "Server not ready" - elif not self.wallet.up_to_date: - self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU) - text = "Synchronizing..." - else: - self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) - self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks)) - c, u = self.wallet.get_balance() - text = "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) ) - if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() ) - else: - self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) - self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(interface.host, self.wallet.blocks)) - text = "Not connected" - - self.status_bar.pop(self.context_id) - self.status_bar.push(self.context_id, text) - - if self.wallet.was_updated and self.wallet.up_to_date: - self.update_history_tab() - self.update_receiving_tab() - # addressbook too... - self.info.set_text( self.wallet.banner ) - self.wallet.was_updated = False - - - def update_receiving_tab(self): - self.recv_list.clear() - for address in self.wallet.all_addresses(): - if self.wallet.is_change(address):continue - label = self.wallet.labels.get(address) - n = 0 - h = self.wallet.history.get(address,[]) - for item in h: - if not item['is_input'] : n=n+1 - tx = "None" if n==0 else "%d"%n - self.recv_list.append((address, label, tx )) - - def update_sending_tab(self): - # detect addresses that are not mine in history, add them here... - self.addressbook_list.clear() - for alias, v in self.wallet.aliases.items(): - s, target = v - label = self.wallet.labels.get(alias) - self.addressbook_list.append((alias, label, '-')) - - for address in self.wallet.addressbook: - label = self.wallet.labels.get(address) - n = 0 - for item in self.wallet.tx_history.values(): - if address in item['outputs'] : n=n+1 - tx = "None" if n==0 else "%d"%n - self.addressbook_list.append((address, label, tx)) - - def update_history_tab(self): - cursor = self.history_treeview.get_cursor()[0] - self.history_list.clear() - balance = 0 - for tx in self.wallet.get_tx_history(): - tx_hash = tx['tx_hash'] - if tx['height']: - conf = self.wallet.blocks - tx['height'] + 1 - time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3] - conf_icon = gtk.STOCK_APPLY - else: - conf = 0 - time_str = 'pending' - conf_icon = gtk.STOCK_EXECUTE - v = tx['value'] - balance += v - label = self.wallet.labels.get(tx_hash) - is_default_label = (label == '') or (label is None) - if is_default_label: label = tx['default_label'] - tooltip = tx_hash + "\n%d confirmations"%conf - - # tx = self.wallet.tx_history.get(tx_hash) - details = "Transaction Details:\n\n" \ - + "Transaction ID:\n" + tx_hash + "\n\n" \ - + "Status: %d confirmations\n\n"%conf \ - + "Date: %s\n\n"%time_str \ - + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \ - + "Outputs:\n-"+ '\n-'.join(tx['outputs']) - r = self.wallet.receipts.get(tx_hash) - if r: - details += "\n_______________________________________" \ - + '\n\nSigned URI: ' + r[2] \ - + "\n\nSigned by: " + r[0] \ - + '\n\nSignature: ' + r[1] - - - self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label, - format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros), tooltip, details] ) - if cursor: self.history_treeview.set_cursor( cursor ) - - - - def newaddress_dialog(self, w): - - title = "New Contact" - dialog = gtk.Dialog(title, parent=self.window, - flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, - buttons= ("cancel", 0, "ok",1) ) - dialog.show() - - label = gtk.HBox() - label_label = gtk.Label('Label:') - label_label.set_size_request(120,10) - label_label.show() - label.pack_start(label_label) - label_entry = gtk.Entry() - label_entry.show() - label.pack_start(label_entry) - label.show() - dialog.vbox.pack_start(label, False, True, 5) - - address = gtk.HBox() - address_label = gtk.Label('Address:') - address_label.set_size_request(120,10) - address_label.show() - address.pack_start(address_label) - address_entry = gtk.Entry() - address_entry.show() - address.pack_start(address_entry) - address.show() - dialog.vbox.pack_start(address, False, True, 5) - - result = dialog.run() - address = address_entry.get_text() - label = label_entry.get_text() - dialog.destroy() - - if result == 1: - if self.wallet.is_valid(address): - self.wallet.addressbook.append(address) - if label: self.wallet.labels[address] = label - self.wallet.save() - self.update_sending_tab() - else: - errorDialog = gtk.MessageDialog( - parent=self.window, - flags=gtk.DIALOG_MODAL, - buttons= gtk.BUTTONS_CLOSE, - message_format = "Invalid address") - errorDialog.show() - errorDialog.run() - errorDialog.destroy() - - - -class ElectrumGui(): - - def __init__(self, wallet): - self.wallet = wallet - - def main(self, url=None): - ew = ElectrumWindow(self.wallet) - if url: ew.set_url(url) - gtk.main() - - def restore_or_create(self): - return restore_create_dialog(self.wallet) diff --git a/gui_qt.py b/gui_qt.py deleted file mode 100644 index 818c602..0000000 --- a/gui_qt.py +++ /dev/null @@ -1,1222 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2012 thomasv@gitorious -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import sys, time, datetime, re - -# todo: see PySide - -from PyQt4.QtGui import * -from PyQt4.QtCore import * -import PyQt4.QtCore as QtCore -import PyQt4.QtGui as QtGui -from interface import DEFAULT_SERVERS - -try: - import icons_rc -except: - print "Could not import icons_rp.py" - print "Please generate it with: 'pyrcc4 icons.qrc -o icons_rc.py'" - sys.exit(1) - -from wallet import format_satoshis -from decimal import Decimal - -import platform -MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace' - - -def numbify(entry, is_int = False): - text = unicode(entry.text()).strip() - chars = '0123456789' - if not is_int: chars +='.' - s = ''.join([i for i in text if i in chars]) - if not is_int: - if '.' in s: - p = s.find('.') - s = s.replace('.','') - s = s[:p] + '.' + s[p:p+8] - try: - amount = int( Decimal(s) * 100000000 ) - except: - amount = None - else: - try: - amount = int( s ) - except: - amount = None - entry.setText(s) - return amount - - -class Timer(QtCore.QThread): - def run(self): - while True: - self.emit(QtCore.SIGNAL('timersignal')) - time.sleep(0.5) - -class EnterButton(QPushButton): - def __init__(self, text, func): - QPushButton.__init__(self, text) - self.func = func - self.clicked.connect(func) - - def keyPressEvent(self, e): - if e.key() == QtCore.Qt.Key_Return: - apply(self.func,()) - -class StatusBarButton(QPushButton): - def __init__(self, icon, tooltip, func): - QPushButton.__init__(self, icon, '') - self.setToolTip(tooltip) - self.setFlat(True) - self.setMaximumWidth(25) - self.clicked.connect(func) - self.func = func - - def keyPressEvent(self, e): - if e.key() == QtCore.Qt.Key_Return: - apply(self.func,()) - - -class QRCodeWidget(QWidget): - - def __init__(self, addr): - super(QRCodeWidget, self).__init__() - self.setGeometry(300, 300, 350, 350) - self.set_addr(addr) - - def set_addr(self, addr): - import pyqrnative - self.addr = addr - self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L) - self.qr.addData(addr) - self.qr.make() - - def paintEvent(self, e): - qp = QtGui.QPainter() - qp.begin(self) - boxsize = 7 - size = self.qr.getModuleCount()*boxsize - k = self.qr.getModuleCount() - black = QColor(0, 0, 0, 255) - white = QColor(255, 255, 255, 255) - for r in range(k): - for c in range(k): - if self.qr.isDark(r, c): - qp.setBrush(black) - qp.setPen(black) - else: - qp.setBrush(white) - qp.setPen(white) - qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize) - qp.end() - - - -def ok_cancel_buttons(dialog): - hbox = QHBoxLayout() - hbox.addStretch(1) - b = QPushButton("OK") - hbox.addWidget(b) - b.clicked.connect(dialog.accept) - b = QPushButton("Cancel") - hbox.addWidget(b) - b.clicked.connect(dialog.reject) - return hbox - - -class ElectrumWindow(QMainWindow): - - def __init__(self, wallet): - QMainWindow.__init__(self) - self.wallet = wallet - self.wallet.gui_callback = self.update_callback - - self.funds_error = False - - self.tabs = tabs = QTabWidget(self) - tabs.addTab(self.create_history_tab(), 'History') - tabs.addTab(self.create_send_tab(), 'Send') - tabs.addTab(self.create_receive_tab(), 'Receive') - tabs.addTab(self.create_contacts_tab(),'Contacts') - tabs.addTab(self.create_wall_tab(), 'Wall') - tabs.setMinimumSize(600, 400) - tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.setCentralWidget(tabs) - self.create_status_bar() - self.setGeometry(100,100,840,400) - self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version ) - self.show() - - QShortcut(QKeySequence("Ctrl+W"), self, self.close) - QShortcut(QKeySequence("Ctrl+Q"), self, self.close) - QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() )) - QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() )) - - self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet) - - - def connect_slots(self, sender): - self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient) - self.previous_payto_e='' - - def check_recipient(self): - if self.payto_e.hasFocus(): - return - r = unicode( self.payto_e.text() ) - if r != self.previous_payto_e: - self.previous_payto_e = r - r = r.strip() - if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r): - try: - to_address = self.wallet.get_alias(r, True, self.show_message, self.question) - except: - return - if to_address: - s = r + ' <' + to_address + '>' - self.payto_e.setText(s) - - - def update_callback(self): - self.emit(QtCore.SIGNAL('updatesignal')) - - def update_wallet(self): - if self.wallet.interface.is_connected: - if self.wallet.blocks == -1: - text = "Connecting..." - icon = QIcon(":icons/status_disconnected.png") - elif self.wallet.blocks == 0: - text = "Server not ready" - icon = QIcon(":icons/status_disconnected.png") - elif not self.wallet.up_to_date: - text = "Synchronizing..." - icon = QIcon(":icons/status_waiting.png") - else: - c, u = self.wallet.get_balance() - text = "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) ) - if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() ) - icon = QIcon(":icons/status_connected.png") - else: - text = "Not connected" - icon = QIcon(":icons/status_disconnected.png") - - if self.funds_error: - text = "Not enough funds" - - self.statusBar().showMessage(text) - self.status_button.setIcon( icon ) - - if self.wallet.up_to_date: - self.textbox.setText( self.wallet.banner ) - self.update_history_tab() - self.update_receive_tab() - self.update_contacts_tab() - - - def create_history_tab(self): - self.history_list = w = QTreeWidget(self) - #print w.getContentsMargins() - w.setColumnCount(5) - w.setColumnWidth(0, 40) - w.setColumnWidth(1, 140) - w.setColumnWidth(2, 350) - w.setColumnWidth(3, 140) - w.setColumnWidth(4, 140) - w.setHeaderLabels( [ '', 'Date', 'Description', 'Amount', 'Balance'] ) - self.connect(w, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.tx_details) - self.connect(w, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked) - self.connect(w, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed) - return w - - def tx_details(self, item, column): - tx_hash = str(item.toolTip(0)) - tx = self.wallet.tx_history.get(tx_hash) - - if tx['height']: - conf = self.wallet.blocks - tx['height'] + 1 - time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3] - else: - conf = 0 - time_str = 'pending' - - tx_details = "Transaction Details:\n\n" \ - + "Transaction ID:\n" + tx_hash + "\n\n" \ - + "Status: %d confirmations\n\n"%conf \ - + "Date: %s\n\n"%time_str \ - + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \ - + "Outputs:\n-"+ '\n-'.join(tx['outputs']) - - r = self.wallet.receipts.get(tx_hash) - if r: - tx_details += "\n_______________________________________" \ - + '\n\nSigned URI: ' + r[2] \ - + "\n\nSigned by: " + r[0] \ - + '\n\nSignature: ' + r[1] - - QMessageBox.information(self, 'Details', tx_details, 'OK') - - - def tx_label_clicked(self, item, column): - if column==2 and item.isSelected(): - tx_hash = str(item.toolTip(0)) - self.is_edit=True - #if not self.wallet.labels.get(tx_hash): item.setText(2,'') - item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) - self.history_list.editItem( item, column ) - item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) - self.is_edit=False - - def tx_label_changed(self, item, column): - if self.is_edit: - return - self.is_edit=True - tx_hash = str(item.toolTip(0)) - tx = self.wallet.tx_history.get(tx_hash) - s = self.wallet.labels.get(tx_hash) - text = unicode( item.text(2) ) - if text: - self.wallet.labels[tx_hash] = text - item.setForeground(2, QBrush(QColor('black'))) - else: - if s: self.wallet.labels.pop(tx_hash) - text = tx['default_label'] - item.setText(2, text) - item.setForeground(2, QBrush(QColor('gray'))) - self.is_edit=False - - def address_label_clicked(self, item, column, l): - if column==1 and item.isSelected(): - addr = unicode( item.text(0) ) - if addr in map(lambda x:x[1], self.wallet.aliases.values()): - return - item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) - l.editItem( item, column ) - item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) - - def address_label_changed(self, item, column, l): - addr = unicode( item.text(0) ) - text = unicode( item.text(1) ) - if text: - self.wallet.labels[addr] = text - else: - s = self.wallet.labels.get(addr) - if s: self.wallet.labels.pop(addr) - self.update_history_tab() - - def update_history_tab(self): - self.history_list.clear() - balance = 0 - for tx in self.wallet.get_tx_history(): - tx_hash = tx['tx_hash'] - if tx['height']: - conf = self.wallet.blocks - tx['height'] + 1 - time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3] - icon = QIcon(":icons/confirmed.png") - else: - conf = 0 - time_str = 'pending' - icon = QIcon(":icons/unconfirmed.png") - v = tx['value'] - balance += v - label = self.wallet.labels.get(tx_hash) - is_default_label = (label == '') or (label is None) - if is_default_label: label = tx['default_label'] - - item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] ) - item.setFont(2, QFont(MONOSPACE_FONT)) - item.setFont(3, QFont(MONOSPACE_FONT)) - item.setFont(4, QFont(MONOSPACE_FONT)) - item.setToolTip(0, tx_hash) - if is_default_label: - item.setForeground(2, QBrush(QColor('grey'))) - - item.setIcon(0, icon) - self.history_list.insertTopLevelItem(0,item) - - - def create_send_tab(self): - w = QWidget() - - grid = QGridLayout() - grid.setSpacing(8) - grid.setColumnMinimumWidth(3,300) - grid.setColumnStretch(4,1) - - self.payto_e = QLineEdit() - grid.addWidget(QLabel('Pay to'), 1, 0) - grid.addWidget(self.payto_e, 1, 1, 1, 3) - - self.message_e = QLineEdit() - grid.addWidget(QLabel('Description'), 2, 0) - grid.addWidget(self.message_e, 2, 1, 1, 3) - - self.amount_e = QLineEdit() - grid.addWidget(QLabel('Amount'), 3, 0) - grid.addWidget(self.amount_e, 3, 1, 1, 2) - - self.fee_e = QLineEdit() - grid.addWidget(QLabel('Fee'), 4, 0) - grid.addWidget(self.fee_e, 4, 1, 1, 2) - - b = EnterButton("Send", self.do_send) - grid.addWidget(b, 5, 1) - - b = EnterButton("Clear",self.do_clear) - grid.addWidget(b, 5, 2) - - self.payto_sig = QLabel('') - grid.addWidget(self.payto_sig, 6, 0, 1, 4) - - w.setLayout(grid) - w.show() - - w2 = QWidget() - vbox = QVBoxLayout() - vbox.addWidget(w) - vbox.addStretch(1) - w2.setLayout(vbox) - - def entry_changed( is_fee ): - self.funds_error = False - amount = numbify(self.amount_e) - fee = numbify(self.fee_e) - if not is_fee: fee = None - if amount is None: - return - inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee ) - if not is_fee: - self.fee_e.setText( str( Decimal( fee ) / 100000000 ) ) - if inputs: - palette = QPalette() - palette.setColor(self.amount_e.foregroundRole(), QColor('black')) - else: - palette = QPalette() - palette.setColor(self.amount_e.foregroundRole(), QColor('red')) - self.funds_error = True - self.amount_e.setPalette(palette) - self.fee_e.setPalette(palette) - - self.amount_e.textChanged.connect(lambda: entry_changed(False) ) - self.fee_e.textChanged.connect(lambda: entry_changed(True) ) - - return w2 - - def do_send(self): - - label = unicode( self.message_e.text() ) - r = unicode( self.payto_e.text() ) - r = r.strip() - - m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r) - m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) - - if m1: - to_address = self.wallet.get_alias(r, True, self.show_message, self.question) - if not to_address: - return - elif m2: - to_address = m2.group(5) - else: - to_address = r - - if not self.wallet.is_valid(to_address): - QMessageBox.warning(self, 'Error', 'Invalid Bitcoin Address:\n'+to_address, 'OK') - return - - try: - amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 ) - except: - QMessageBox.warning(self, 'Error', 'Invalid Amount', 'OK') - return - try: - fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 ) - except: - QMessageBox.warning(self, 'Error', 'Invalid Fee', 'OK') - return - - if self.wallet.use_encryption: - password = self.password_dialog() - if not password: - return - else: - password = None - - try: - tx = self.wallet.mktx( to_address, amount, label, password, fee ) - except BaseException, e: - self.show_message(e.message) - return - - status, msg = self.wallet.sendtx( tx ) - if status: - QMessageBox.information(self, '', 'Payment sent.\n'+msg, 'OK') - self.do_clear() - self.update_contacts_tab() - else: - QMessageBox.warning(self, 'Error', msg, 'OK') - - - def set_url(self, url): - payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question) - self.tabs.setCurrentIndex(1) - self.payto_e.setText(payto) - self.message_e.setText(message) - self.amount_e.setText(amount) - if identity: - self.set_frozen(self.payto_e,True) - self.set_frozen(self.amount_e,True) - self.set_frozen(self.message_e,True) - self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity ) - else: - self.payto_sig.setVisible(False) - - def do_clear(self): - self.payto_sig.setVisible(False) - for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]: - e.setText('') - self.set_frozen(e,False) - - def set_frozen(self,entry,frozen): - if frozen: - entry.setReadOnly(True) - entry.setFrame(False) - palette = QPalette() - palette.setColor(entry.backgroundRole(), QColor('lightgray')) - entry.setPalette(palette) - else: - entry.setReadOnly(False) - entry.setFrame(True) - palette = QPalette() - palette.setColor(entry.backgroundRole(), QColor('white')) - entry.setPalette(palette) - - - - - def clear_buttons(self, hbox): - while hbox.count(): hbox.removeItem(hbox.itemAt(0)) - - def add_buttons(self, l, hbox, is_recv): - self.clear_buttons(hbox) - - i = l.currentItem() - if not i: return - addr = unicode( i.text(0) ) - - hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(addr))) - hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(addr))) - if is_recv: - def toggle_freeze(addr): - if addr in self.wallet.frozen_addresses: - self.wallet.frozen_addresses.remove(addr) - else: - self.wallet.frozen_addresses.append(addr) - self.wallet.save() - self.update_receive_tab() - - t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze" - hbox.addWidget(EnterButton(t, lambda: toggle_freeze(addr))) - - else: - def payto(addr): - if not addr:return - self.tabs.setCurrentIndex(1) - self.payto_e.setText(addr) - self.amount_e.setFocus() - hbox.addWidget(EnterButton('Pay to', lambda: payto(addr))) - hbox.addWidget(EnterButton("New", self.newaddress_dialog)) - hbox.addStretch(1) - - - def create_receive_tab(self): - l = QTreeWidget(self) - l.setColumnCount(4) - l.setColumnWidth(0, 350) - l.setColumnWidth(1, 330) - l.setColumnWidth(2, 100) - l.setColumnWidth(3, 10) - l.setHeaderLabels( ['Address', 'Label','Balance','Tx']) - - w = QWidget() - vbox = QVBoxLayout() - w.setLayout(vbox) - - vbox.setMargin(0) - vbox.setSpacing(0) - vbox.addWidget(l) - buttons = QWidget() - vbox.addWidget(buttons) - - hbox = QHBoxLayout() - hbox.setMargin(0) - hbox.setSpacing(0) - buttons.setLayout(hbox) - - self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) - self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) - self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, True)) - self.receive_list = l - self.receive_buttons_hbox = hbox - return w - - def create_contacts_tab(self): - l = QTreeWidget(self) - l.setColumnCount(3) - l.setColumnWidth(0, 350) - l.setColumnWidth(1, 330) - l.setColumnWidth(2, 20) - l.setHeaderLabels( ['Address', 'Label','Tx']) - - w = QWidget() - vbox = QVBoxLayout() - w.setLayout(vbox) - - vbox.setMargin(0) - vbox.setSpacing(0) - vbox.addWidget(l) - buttons = QWidget() - vbox.addWidget(buttons) - - hbox = QHBoxLayout() - hbox.setMargin(0) - hbox.setSpacing(0) - buttons.setLayout(hbox) - - self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) - self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) - self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details) - self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, False)) - - self.contacts_list = l - self.contacts_buttons_hbox = hbox - return w - - def update_receive_tab(self): - self.receive_list.clear() - self.clear_buttons(self.receive_buttons_hbox) - - for address in self.wallet.all_addresses(): - if self.wallet.is_change(address):continue - label = self.wallet.labels.get(address,'') - n = 0 - h = self.wallet.history.get(address,[]) - for item in h: - if not item['is_input'] : n=n+1 - tx = "None" if n==0 else "%d"%n - - c, u = self.wallet.get_addr_balance(address) - balance = format_satoshis( c + u, False, self.wallet.num_zeros ) - if address in self.wallet.frozen_addresses: - balance += '[F]' - - item = QTreeWidgetItem( [ address, label, balance, tx] ) - item.setFont(0, QFont(MONOSPACE_FONT)) - self.receive_list.addTopLevelItem(item) - - def show_contact_details(self, item, column): - m = unicode(item.text(0)) - a = self.wallet.aliases.get(m) - if a: - if a[0] in self.wallet.authorities.keys(): - s = self.wallet.authorities.get(a[0]) - else: - s = "self-signed" - msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] - QMessageBox.information(self, 'Alias', msg, 'OK') - - def update_contacts_tab(self): - self.contacts_list.clear() - self.clear_buttons(self.contacts_buttons_hbox) - - for alias, v in self.wallet.aliases.items(): - s, target = v - item = QTreeWidgetItem( [ target, alias, '-'] ) - self.contacts_list.addTopLevelItem(item) - - for address in self.wallet.addressbook: - label = self.wallet.labels.get(address,'') - n = 0 - for item in self.wallet.tx_history.values(): - if address in item['outputs'] : n=n+1 - tx = "None" if n==0 else "%d"%n - item = QTreeWidgetItem( [ address, label, tx] ) - item.setFont(0, QFont(MONOSPACE_FONT)) - self.contacts_list.addTopLevelItem(item) - - - def create_wall_tab(self): - self.textbox = textbox = QTextEdit(self) - textbox.setFont(QFont(MONOSPACE_FONT)) - textbox.setReadOnly(True) - return textbox - - def create_status_bar(self): - sb = QStatusBar() - sb.setFixedHeight(35) - sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) ) - sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) ) - sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) ) - self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) - sb.addPermanentWidget( self.status_button ) - self.setStatusBar(sb) - - def newaddress_dialog(self): - text, ok = QInputDialog.getText(self, 'New Contact', 'Address:') - address = unicode(text) - if ok: - if self.wallet.is_valid(address): - self.wallet.addressbook.append(address) - self.wallet.save() - self.update_contacts_tab() - else: - QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK') - - @staticmethod - def show_seed_dialog(wallet, parent=None): - import mnemonic - if wallet.use_encryption: - password = parent.password_dialog() - if not password: return - else: - password = None - - try: - seed = wallet.pw_decode( wallet.seed, password) - except: - QMessageBox.warning(parent, 'Error', 'Invalid Password', 'OK') - return - - msg = "Your wallet generation seed is:\n\n" + seed \ - + "\n\nPlease keep it in a safe place; if you lose it,\nyou will not be able to restore your wallet.\n\n" \ - + "Equivalently, your wallet seed can be stored and\nrecovered with the following mnemonic code:\n\n\"" \ - + ' '.join(mnemonic.mn_encode(seed)) + "\"\n\n\n" - - d = QDialog(None) - d.setModal(1) - d.setWindowTitle("Seed") - d.setMinimumSize(400, 270) - - vbox = QVBoxLayout() - hbox = QHBoxLayout() - vbox2 = QVBoxLayout() - l = QLabel() - l.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56)) - vbox2.addWidget(l) - vbox2.addStretch(1) - hbox.addLayout(vbox2) - hbox.addWidget(QLabel(msg)) - vbox.addLayout(hbox) - - hbox = QHBoxLayout() - hbox.addStretch(1) - - - if parent: - app = parent.app - else: - app = QApplication - - b = QPushButton("Copy to Clipboard") - b.clicked.connect(lambda: app.clipboard().setText(' '.join(mnemonic.mn_encode(seed)))) - hbox.addWidget(b) - b = QPushButton("View as QR Code") - b.clicked.connect(lambda: ElectrumWindow.show_seed_qrcode(seed)) - hbox.addWidget(b) - - b = QPushButton("OK") - b.clicked.connect(d.accept) - hbox.addWidget(b) - vbox.addLayout(hbox) - d.setLayout(vbox) - d.exec_() - - @staticmethod - def show_seed_qrcode(seed): - if not seed: return - d = QDialog(None) - d.setModal(1) - d.setWindowTitle("Seed") - d.setMinimumSize(270, 300) - vbox = QVBoxLayout() - vbox.addWidget(QRCodeWidget(seed)) - hbox = QHBoxLayout() - hbox.addStretch(1) - b = QPushButton("OK") - hbox.addWidget(b) - b.clicked.connect(d.accept) - - vbox.addLayout(hbox) - d.setLayout(vbox) - d.exec_() - - def show_address_qrcode(self,address): - if not address: return - d = QDialog(None) - d.setModal(1) - d.setWindowTitle(address) - d.setMinimumSize(270, 350) - vbox = QVBoxLayout() - qrw = QRCodeWidget(address) - vbox.addWidget(qrw) - - hbox = QHBoxLayout() - amount_e = QLineEdit() - hbox.addWidget(QLabel('Amount')) - hbox.addWidget(amount_e) - vbox.addLayout(hbox) - - #hbox = QHBoxLayout() - #label_e = QLineEdit() - #hbox.addWidget(QLabel('Label')) - #hbox.addWidget(label_e) - #vbox.addLayout(hbox) - - def amount_changed(): - amount = numbify(amount_e) - #label = str( label_e.getText() ) - if amount is not None: - qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000))) - else: - qrw.set_addr( address ) - qrw.repaint() - - def do_save(): - import bmp - bmp.save_qrcode(qrw.qr, "qrcode.bmp") - self.show_message("QR code saved to file 'qrcode.bmp'") - - amount_e.textChanged.connect( amount_changed ) - - hbox = QHBoxLayout() - hbox.addStretch(1) - b = QPushButton("Save") - b.clicked.connect(do_save) - hbox.addWidget(b) - b = QPushButton("Close") - hbox.addWidget(b) - b.clicked.connect(d.accept) - - vbox.addLayout(hbox) - d.setLayout(vbox) - d.exec_() - - def question(self, msg): - return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes - - def show_message(self, msg): - QMessageBox.information(self, 'Message', msg, 'OK') - - def password_dialog(self ): - d = QDialog(self) - d.setModal(1) - - pw = QLineEdit() - pw.setEchoMode(2) - - vbox = QVBoxLayout() - msg = 'Please enter your password' - vbox.addWidget(QLabel(msg)) - - grid = QGridLayout() - grid.setSpacing(8) - grid.addWidget(QLabel('Password'), 1, 0) - grid.addWidget(pw, 1, 1) - vbox.addLayout(grid) - - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - - if not d.exec_(): return - return unicode(pw.text()) - - @staticmethod - def change_password_dialog( wallet, parent=None ): - d = QDialog(parent) - d.setModal(1) - - pw = QLineEdit() - pw.setEchoMode(2) - new_pw = QLineEdit() - new_pw.setEchoMode(2) - conf_pw = QLineEdit() - conf_pw.setEchoMode(2) - - vbox = QVBoxLayout() - if parent: - msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' - else: - msg = "Please choose a password to encrypt your wallet keys.\nLeave these fields empty if you want to disable encryption." - vbox.addWidget(QLabel(msg)) - - grid = QGridLayout() - grid.setSpacing(8) - - if wallet.use_encryption: - grid.addWidget(QLabel('Password'), 1, 0) - grid.addWidget(pw, 1, 1) - - grid.addWidget(QLabel('New Password'), 2, 0) - grid.addWidget(new_pw, 2, 1) - - grid.addWidget(QLabel('Confirm Password'), 3, 0) - grid.addWidget(conf_pw, 3, 1) - vbox.addLayout(grid) - - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - - if not d.exec_(): return - - password = unicode(pw.text()) if wallet.use_encryption else None - new_password = unicode(new_pw.text()) - new_password2 = unicode(conf_pw.text()) - - try: - seed = wallet.pw_decode( wallet.seed, password) - except: - QMessageBox.warning(parent, 'Error', 'Incorrect Password', 'OK') - return - - if new_password != new_password2: - QMessageBox.warning(parent, 'Error', 'Passwords do not match', 'OK') - return - - wallet.update_password(seed, new_password) - - @staticmethod - def seed_dialog(wallet, parent=None): - d = QDialog(parent) - d.setModal(1) - - vbox = QVBoxLayout() - msg = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet." - vbox.addWidget(QLabel(msg)) - - grid = QGridLayout() - grid.setSpacing(8) - - seed_e = QLineEdit() - grid.addWidget(QLabel('Seed or mnemonic'), 1, 0) - grid.addWidget(seed_e, 1, 1) - - gap_e = QLineEdit() - gap_e.setText("5") - grid.addWidget(QLabel('Gap limit'), 2, 0) - grid.addWidget(gap_e, 2, 1) - gap_e.textChanged.connect(lambda: numbify(gap_e,True)) - vbox.addLayout(grid) - - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - - if not d.exec_(): return - - try: - gap = int(unicode(gap_e.text())) - except: - QMessageBox.warning(None, 'Error', 'error', 'OK') - sys.exit(0) - - try: - seed = unicode(seed_e.text()) - seed.decode('hex') - except: - import mnemonic - print "not hex, trying decode" - try: - seed = mnemonic.mn_decode( seed.split(' ') ) - except: - QMessageBox.warning(None, 'Error', 'I cannot decode this', 'OK') - sys.exit(0) - if not seed: - QMessageBox.warning(None, 'Error', 'no seed', 'OK') - sys.exit(0) - - wallet.seed = str(seed) - #print repr(wallet.seed) - wallet.gap_limit = gap - return True - - - def settings_dialog(self): - d = QDialog(self) - d.setModal(1) - - vbox = QVBoxLayout() - - msg = 'Here are the settings of your wallet.' - vbox.addWidget(QLabel(msg)) - - grid = QGridLayout() - grid.setSpacing(8) - vbox.addLayout(grid) - - fee_e = QLineEdit() - fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) ) - grid.addWidget(QLabel('Fee per tx. input'), 2, 0) - grid.addWidget(fee_e, 2, 1) - fee_e.textChanged.connect(lambda: numbify(fee_e,False)) - - nz_e = QLineEdit() - nz_e.setText("%d"% self.wallet.num_zeros) - grid.addWidget(QLabel('Zeros displayed after decimal point'), 3, 0) - grid.addWidget(nz_e, 3, 1) - nz_e.textChanged.connect(lambda: numbify(nz_e,True)) - - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - - if not d.exec_(): return - - fee = unicode(fee_e.text()) - try: - fee = int( 100000000 * Decimal(fee) ) - except: - QMessageBox.warning(self, 'Error', 'Invalid value:%s'%fee, 'OK') - return - - if self.wallet.fee != fee: - self.wallet.fee = fee - self.wallet.save() - - nz = unicode(nz_e.text()) - try: - nz = int( nz ) - if nz>8: nz=8 - except: - QMessageBox.warning(self, 'Error', 'Invalid value:%s'%nz, 'OK') - return - - if self.wallet.num_zeros != nz: - self.wallet.num_zeros = nz - self.update_history_tab() - self.update_receive_tab() - self.wallet.save() - - @staticmethod - def network_dialog(wallet, parent=None): - interface = wallet.interface - if parent: - if interface.is_connected: - status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks) - else: - status = "Not connected" - server = wallet.server - else: - import random - status = "Please choose a server." - server = random.choice( DEFAULT_SERVERS ) - - if not wallet.interface.servers: - servers_list = [] - for x in DEFAULT_SERVERS: - h,port,protocol = x.split(':') - servers_list.append( (h,[(protocol,port)] ) ) - else: - servers_list = wallet.interface.servers - - plist = {} - for item in servers_list: - host, pp = item - z = {} - for item2 in pp: - protocol, port = item2 - z[protocol] = port - plist[host] = z - - d = QDialog(parent) - d.setModal(1) - d.setWindowTitle('Server') - d.setMinimumSize(375, 20) - - vbox = QVBoxLayout() - vbox.setSpacing(20) - - hbox = QHBoxLayout() - l = QLabel() - l.setPixmap(QPixmap(":icons/network.png")) - hbox.addWidget(l) - hbox.addWidget(QLabel(status)) - - vbox.addLayout(hbox) - - hbox = QHBoxLayout() - host_line = QLineEdit() - host_line.setText(server) - hbox.addWidget(QLabel('Connect to:')) - hbox.addWidget(host_line) - vbox.addLayout(hbox) - - hbox = QHBoxLayout() - - buttonGroup = QGroupBox("protocol") - radio1 = QRadioButton("tcp", buttonGroup) - radio2 = QRadioButton("http", buttonGroup) - - def current_line(): - return unicode(host_line.text()).split(':') - - def set_button(protocol): - if protocol == 't': - radio1.setChecked(1) - elif protocol == 'h': - radio2.setChecked(1) - - def set_protocol(protocol): - host = current_line()[0] - pp = plist[host] - if protocol not in pp.keys(): - protocol = pp.keys()[0] - set_button(protocol) - port = pp[protocol] - host_line.setText( host + ':' + port + ':' + protocol) - - radio1.clicked.connect(lambda x: set_protocol('t') ) - radio2.clicked.connect(lambda x: set_protocol('h') ) - - set_button(current_line()[2]) - - hbox.addWidget(QLabel('Protocol:')) - hbox.addWidget(radio1) - hbox.addWidget(radio2) - - vbox.addLayout(hbox) - - if wallet.interface.servers: - label = 'Active Servers' - else: - label = 'Default Servers' - - servers_list_widget = QTreeWidget(parent) - servers_list_widget.setHeaderLabels( [ label ] ) - servers_list_widget.setMaximumHeight(150) - for host in plist.keys(): - servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] )) - - def do_set_line(x): - host = unicode(x.text(0)) - pp = plist[host] - if 't' in pp.keys(): - protocol = 't' - else: - protocol = pp.keys()[0] - port = pp[protocol] - host_line.setText( host + ':' + port + ':' + protocol) - set_button(protocol) - - servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line) - vbox.addWidget(servers_list_widget) - - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - - if not d.exec_(): return - server = unicode( host_line.text() ) - - try: - wallet.set_server(server) - except: - QMessageBox.information(None, 'Error', 'error', 'OK') - if parent == None: - sys.exit(1) - else: - return - - return True - - - -class ElectrumGui(): - - def __init__(self, wallet): - self.wallet = wallet - self.app = QApplication(sys.argv) - - def waiting_dialog(self): - - s = Timer() - s.start() - w = QDialog() - w.resize(200, 70) - w.setWindowTitle('Electrum') - l = QLabel('') - vbox = QVBoxLayout() - vbox.addWidget(l) - w.setLayout(vbox) - w.show() - def f(): - if self.wallet.up_to_date: w.close() - else: - l.setText("Please wait...\nGenerating addresses: %d"%len(self.wallet.all_addresses())) - pass - w.connect(s, QtCore.SIGNAL('timersignal'), f) - self.wallet.interface.poke() - w.exec_() - w.destroy() - - - def restore_or_create(self): - - msg = "Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?" - r = QMessageBox.question(None, 'Message', msg, 'create', 'restore', 'cancel', 0, 2) - if r==2: return False - - is_recovery = (r==1) - wallet = self.wallet - # ask for the server. - if not ElectrumWindow.network_dialog( wallet, parent=None ): return False - - if not is_recovery: - wallet.new_seed(None) - wallet.init_mpk( wallet.seed ) - wallet.up_to_date_event.clear() - wallet.up_to_date = False - self.waiting_dialog() - # run a dialog indicating the seed, ask the user to remember it - ElectrumWindow.show_seed_dialog(wallet) - #ask for password - ElectrumWindow.change_password_dialog(wallet) - else: - # ask for seed and gap. - if not ElectrumWindow.seed_dialog( wallet ): return False - wallet.init_mpk( wallet.seed ) - wallet.up_to_date_event.clear() - wallet.up_to_date = False - self.waiting_dialog() - if wallet.is_found(): - # history and addressbook - wallet.update_tx_history() - wallet.fill_addressbook() - print "recovery successful" - wallet.save() - else: - QMessageBox.information(None, 'Message', "No transactions found for this seed", 'OK') - - wallet.save() - return True - - def main(self,url): - s = Timer() - s.start() - w = ElectrumWindow(self.wallet) - if url: w.set_url(url) - w.app = self.app - w.connect_slots(s) - w.update_wallet() - - self.app.exec_() diff --git a/interface.py b/interface.py deleted file mode 100644 index 0ace32b..0000000 --- a/interface.py +++ /dev/null @@ -1,393 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2011 thomasv@gitorious -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import random, socket, ast, re -import threading, traceback, sys, time, json, Queue - -from version import ELECTRUM_VERSION - -DEFAULT_TIMEOUT = 5 -DEFAULT_SERVERS = [ 'ecdsa.org:50001:t', 'electrum.novit.ro:50001:t', 'electrum.bitcoins.sk:50001:t'] # list of default servers - - -def old_to_new(s): - s = s.replace("'blk_hash'", "'block_hash'") - s = s.replace("'pos'", "'index'") - s = s.replace("'nTime'", "'timestamp'") - s = s.replace("'is_in'", "'is_input'") - s = s.replace("'raw_scriptPubKey'","'raw_output_script'") - return s - - -class Interface(threading.Thread): - def __init__(self, host, port): - threading.Thread.__init__(self) - self.daemon = True - self.host = host - self.port = port - - self.servers = [] # actual list from IRC - self.rtime = 0 - - self.is_connected = True - self.poll_interval = 1 - - #json - self.message_id = 0 - self.responses = Queue.Queue() - self.unanswered_requests = {} - - def poke(self): - # push a fake response so that the getting thread exits its loop - self.responses.put(None) - - def queue_json_response(self, c): - - #print "<--",c - msg_id = c.get('id') - error = c.get('error') - - if error: - print "received error:", c - return - - if msg_id is not None: - method, params = self.unanswered_requests.pop(msg_id) - result = c.get('result') - else: - # notification - method = c.get('method') - params = c.get('params') - - if method == 'blockchain.numblocks.subscribe': - result = params[0] - params = [] - - elif method == 'blockchain.address.subscribe': - addr = params[0] - result = params[1] - params = [addr] - - self.responses.put({'method':method, 'params':params, 'result':result}) - - - - def subscribe(self, addresses): - messages = [] - for addr in addresses: - messages.append(('blockchain.address.subscribe', [addr])) - self.send(messages) - - - - -class PollingInterface(Interface): - """ non-persistent connection. synchronous calls""" - - def __init__(self, host, port): - Interface.__init__(self, host, port) - self.session_id = None - - def get_history(self, address): - self.send([('blockchain.address.get_history', [address] )]) - - def poll(self): - pass - #if is_new or wallet.remote_url: - # self.was_updated = True - # is_new = wallet.synchronize() - # wallet.update_tx_history() - # wallet.save() - # return is_new - #else: - # return False - - def run(self): - self.is_connected = True - while self.is_connected: - try: - if self.session_id: - self.poll() - time.sleep(self.poll_interval) - except socket.gaierror: - break - except socket.error: - break - except: - traceback.print_exc(file=sys.stdout) - break - - self.is_connected = False - self.poke() - - - - - - - - - -class HttpStratumInterface(PollingInterface): - - def poll(self): - self.send([]) - - def send(self, messages): - import urllib2, json, time, cookielib - - cj = cookielib.CookieJar() - opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) - urllib2.install_opener(opener) - - t1 = time.time() - - data = [] - for m in messages: - method, params = m - if type(params) != type([]): params = [params] - data.append( { 'method':method, 'id':self.message_id, 'params':params } ) - self.unanswered_requests[self.message_id] = method, params - self.message_id += 1 - - if data: - data_json = json.dumps(data) - else: - # poll with GET - data_json = None - - host = 'http://%s:%d'%( self.host, self.port ) - headers = {'content-type': 'application/json'} - if self.session_id: - headers['cookie'] = 'SESSION=%s'%self.session_id - - req = urllib2.Request(host, data_json, headers) - response_stream = urllib2.urlopen(req) - - for index, cookie in enumerate(cj): - if cookie.name=='SESSION': - self.session_id = cookie.value - - response = response_stream.read() - if response: - response = json.loads( response ) - if type(response) is not type([]): - self.queue_json_response(response) - else: - for item in response: - self.queue_json_response(item) - - if response: - self.poll_interval = 1 - else: - if self.poll_interval < 15: - self.poll_interval += 1 - #print self.poll_interval, response - - self.rtime = time.time() - t1 - self.is_connected = True - - - - -class TcpStratumInterface(Interface): - """json-rpc over persistent TCP connection, asynchronous""" - - def __init__(self, host, port): - Interface.__init__(self, host, port) - self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) - self.s.settimeout(5*60) - self.s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - try: - self.s.connect(( self.host, self.port)) - self.is_connected = True - self.send([('server.version', [ELECTRUM_VERSION])]) - except: - self.is_connected = False - print "not connected" - - def run(self): - try: - out = '' - while self.is_connected: - try: msg = self.s.recv(1024) - except socket.timeout: - # ping the server with server.version, as a real ping does not exist yet - self.send([('server.version', [ELECTRUM_VERSION])]) - continue - out += msg - if msg == '': - self.is_connected = False - print "disconnected." - - while True: - s = out.find('\n') - if s==-1: break - c = out[0:s] - out = out[s+1:] - c = json.loads(c) - self.queue_json_response(c) - - except: - traceback.print_exc(file=sys.stdout) - - self.is_connected = False - print "poking" - self.poke() - - def send(self, messages): - out = '' - for m in messages: - method, params = m - request = json.dumps( { 'id':self.message_id, 'method':method, 'params':params } ) - self.unanswered_requests[self.message_id] = method, params - #print "-->",request - self.message_id += 1 - out += request + '\n' - self.s.send( out ) - - def get_history(self, addr): - self.send([('blockchain.address.get_history', [addr])]) - - - - - -class WalletSynchronizer(threading.Thread): - - def __init__(self, wallet, loop=False): - threading.Thread.__init__(self) - self.daemon = True - self.wallet = wallet - self.loop = loop - self.start_interface() - - - def handle_response(self, r): - if r is None: - return - - method = r['method'] - params = r['params'] - result = r['result'] - - if method == 'server.banner': - self.wallet.banner = result - self.wallet.was_updated = True - - elif method == 'server.peers.subscribe': - servers = [] - for item in result: - s = [] - host = item[1] - ports = [] - if len(item)>2: - for v in item[2]: - if re.match("[th]\d+",v): - ports.append((v[0],v[1:])) - if ports: - servers.append( (host, ports) ) - self.interface.servers = servers - - elif method == 'blockchain.address.subscribe': - addr = params[0] - self.wallet.receive_status_callback(addr, result) - - elif method == 'blockchain.address.get_history': - addr = params[0] - self.wallet.receive_history_callback(addr, result) - self.wallet.was_updated = True - - elif method == 'blockchain.transaction.broadcast': - self.wallet.tx_result = result - self.wallet.tx_event.set() - - elif method == 'blockchain.numblocks.subscribe': - self.wallet.blocks = result - - elif method == 'server.version': - pass - - else: - print "unknown message:", method, params, result - - - def start_interface(self): - try: - host, port, protocol = self.wallet.server.split(':') - port = int(port) - except: - self.wallet.pick_random_server() - host, port, protocol = self.wallet.server.split(':') - port = int(port) - - #print protocol, host, port - if protocol == 't': - InterfaceClass = TcpStratumInterface - elif protocol == 'h': - InterfaceClass = HttpStratumInterface - else: - print "unknown protocol" - InterfaceClass = TcpStratumInterface - - self.interface = InterfaceClass(host, port) - self.interface.start() - self.wallet.interface = self.interface - - if self.interface.is_connected: - self.wallet.start_session(self.interface) - - - - def run(self): - import socket, time - while True: - while self.interface.is_connected: - new_addresses = self.wallet.synchronize() - if new_addresses: - self.interface.subscribe(new_addresses) - - if self.wallet.is_up_to_date(): - if not self.wallet.up_to_date: - self.wallet.up_to_date = True - self.wallet.was_updated = True - self.wallet.up_to_date_event.set() - else: - if self.wallet.up_to_date: - self.wallet.up_to_date = False - self.wallet.was_updated = True - - if self.wallet.was_updated: - self.wallet.gui_callback() - self.wallet.was_updated = False - - response = self.interface.responses.get() - self.handle_response(response) - - print "disconnected, gui callback" - self.wallet.gui_callback() - if self.loop: - time.sleep(5) - self.start_interface() - continue - else: - break - - - diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..1f76a2e --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,3 @@ +from wallet import Wallet, SecretToASecret, format_satoshis +from interface import WalletSynchronizer +from interface import TcpStratumInterface diff --git a/lib/bmp.py b/lib/bmp.py new file mode 100644 index 0000000..b4bd410 --- /dev/null +++ b/lib/bmp.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +""" +bmp.py - module for constructing simple BMP graphics files + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +__version__ = "0.3" +__about = "bmp module, version %s, written by Paul McGuire, October, 2003, updated by Margus Laak, September, 2009" % __version__ + +from math import ceil, hypot + + +def shortToString(i): + hi = (i & 0xff00) >> 8 + lo = i & 0x00ff + return chr(lo) + chr(hi) + +def longToString(i): + hi = (long(i) & 0x7fff0000) >> 16 + lo = long(i) & 0x0000ffff + return shortToString(lo) + shortToString(hi) + +def long24ToString(i): + return chr(i & 0xff) + chr(i >> 8 & 0xff) + chr(i >> 16 & 0xff) + +def stringToLong(input_string, offset): + return ord(input_string[offset+3]) << 24 | ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset]) + +def stringToLong24(input_string, offset): + return ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset]) + +class Color(object): + """class for specifying colors while drawing BitMap elements""" + __slots__ = [ 'red', 'grn', 'blu' ] + __shade = 32 + + def __init__( self, r=0, g=0, b=0 ): + self.red = r + self.grn = g + self.blu = b + + def __setattr__(self, name, value): + if hasattr(self, name): + raise AttributeError, "Color is immutable" + else: + object.__setattr__(self, name, value) + + def __str__( self ): + return "R:%d G:%d B:%d" % (self.red, self.grn, self.blu ) + + def __hash__( self ): + return ( ( long(self.blu) ) + + ( long(self.grn) << 8 ) + + ( long(self.red) << 16 ) ) + + def __eq__( self, other ): + return (self is other) or (self.toLong == other.toLong) + + def lighten( self ): + return Color( + min( self.red + Color.__shade, 255), + min( self.grn + Color.__shade, 255), + min( self.blu + Color.__shade, 255) + ) + + def darken( self ): + return Color( + max( self.red - Color.__shade, 0), + max( self.grn - Color.__shade, 0), + max( self.blu - Color.__shade, 0) + ) + + def toLong( self ): + return self.__hash__() + + def fromLong( l ): + b = l & 0xff + l = l >> 8 + g = l & 0xff + l = l >> 8 + r = l & 0xff + return Color( r, g, b ) + fromLong = staticmethod(fromLong) + +# define class constants for common colors +Color.BLACK = Color( 0, 0, 0 ) +Color.RED = Color( 255, 0, 0 ) +Color.GREEN = Color( 0, 255, 0 ) +Color.BLUE = Color( 0, 0, 255 ) +Color.CYAN = Color( 0, 255, 255 ) +Color.MAGENTA = Color( 255, 0, 255 ) +Color.YELLOW = Color( 255, 255, 0 ) +Color.WHITE = Color( 255, 255, 255 ) +Color.DKRED = Color( 128, 0, 0 ) +Color.DKGREEN = Color( 0, 128, 0 ) +Color.DKBLUE = Color( 0, 0, 128 ) +Color.TEAL = Color( 0, 128, 128 ) +Color.PURPLE = Color( 128, 0, 128 ) +Color.BROWN = Color( 128, 128, 0 ) +Color.GRAY = Color( 128, 128, 128 ) + + +class BitMap(object): + """class for drawing and saving simple Windows bitmap files""" + + LINE_SOLID = 0 + LINE_DASHED = 1 + LINE_DOTTED = 2 + LINE_DOT_DASH=3 + _DASH_LEN = 12.0 + _DOT_LEN = 6.0 + _DOT_DASH_LEN = _DOT_LEN + _DASH_LEN + + def __init__( self, width, height, + bkgd = Color.WHITE, frgd = Color.BLACK ): + self.wd = int( ceil(width) ) + self.ht = int( ceil(height) ) + self.bgcolor = 0 + self.fgcolor = 1 + self.palette = [] + self.palette.append( bkgd.toLong() ) + self.palette.append( frgd.toLong() ) + self.currentPen = self.fgcolor + + tmparray = [ self.bgcolor ] * self.wd + self.bitarray = [ tmparray[:] for i in range( self.ht ) ] + self.currentPen = 1 + + + def plotPoint( self, x, y ): + if ( 0 <= x < self.wd and 0 <= y < self.ht ): + x = int(x) + y = int(y) + self.bitarray[y][x] = self.currentPen + + + def _saveBitMapNoCompression( self ): + line_padding = (4 - (self.wd % 4)) % 4 + + # write bitmap header + _bitmap = "BM" + _bitmap += longToString( 54 + self.ht*(self.wd*3 + line_padding) ) # DWORD size in bytes of the file + _bitmap += longToString( 0 ) # DWORD 0 + _bitmap += longToString( 54 ) + _bitmap += longToString( 40 ) # DWORD header size = 40 + _bitmap += longToString( self.wd ) # DWORD image width + _bitmap += longToString( self.ht ) # DWORD image height + _bitmap += shortToString( 1 ) # WORD planes = 1 + _bitmap += shortToString( 24 ) # WORD bits per pixel = 8 + _bitmap += longToString( 0 ) # DWORD compression = 0 + _bitmap += longToString( self.ht * (self.wd * 3 + line_padding) ) # DWORD sizeimage = size in bytes of the bitmap = width * height + _bitmap += longToString( 0 ) # DWORD horiz pixels per meter (?) + _bitmap += longToString( 0 ) # DWORD ver pixels per meter (?) + _bitmap += longToString( 0 ) # DWORD number of colors used = 256 + _bitmap += longToString( 0 ) # DWORD number of "import colors = len( self.palette ) + + # write pixels + self.bitarray.reverse() + for row in self.bitarray: + for pixel in row: + c = self.palette[pixel] + _bitmap += long24ToString(c) + for i in range(line_padding): + _bitmap += chr( 0 ) + + return _bitmap + + + + def saveFile( self, filename): + _b = self._saveBitMapNoCompression( ) + + f = file(filename, 'wb') + f.write(_b) + f.close() + + +def save_qrcode(qr, filename): + bitmap = BitMap( 35*8, 35*8 ) + #print len(bitmap.bitarray) + bitmap.bitarray = [] + k = 33 + for r in range(35): + tmparray = [ 0 ] * 35*8 + + if 0 < r < 34: + for c in range(k): + if qr.isDark(r-1, c): + tmparray[ (1+c)*8:(2+c)*8] = [1]*8 + + for i in range(8): + bitmap.bitarray.append( tmparray[:] ) + + bitmap.saveFile( filename ) + + + +if __name__ == "__main__": + + bmp = BitMap( 10, 10 ) + bmp.plotPoint( 5, 5 ) + bmp.plotPoint( 0, 0 ) + bmp.saveFile( "test.bmp" ) + diff --git a/lib/gui.py b/lib/gui.py new file mode 100644 index 0000000..bbd61b2 --- /dev/null +++ b/lib/gui.py @@ -0,0 +1,1297 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import datetime +import thread, time, ast, sys, re +import socket, traceback +import pygtk +pygtk.require('2.0') +import gtk, gobject +from electrum import pyqrnative +from decimal import Decimal + +gtk.gdk.threads_init() +APP_NAME = "Electrum" +import platform +MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace' + +from wallet import format_satoshis +from interface import DEFAULT_SERVERS + +def numbify(entry, is_int = False): + text = entry.get_text().strip() + chars = '0123456789' + if not is_int: chars +='.' + s = ''.join([i for i in text if i in chars]) + if not is_int: + if '.' in s: + p = s.find('.') + s = s.replace('.','') + s = s[:p] + '.' + s[p:p+8] + try: + amount = int( Decimal(s) * 100000000 ) + except: + amount = None + else: + try: + amount = int( s ) + except: + amount = None + entry.set_text(s) + return amount + + + + +def show_seed_dialog(wallet, password, parent): + from electrum import mnemonic + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + show_message("Incorrect password") + return + dialog = gtk.MessageDialog( + parent = parent, + flags = gtk.DIALOG_MODAL, + buttons = gtk.BUTTONS_OK, + message_format = "Your wallet generation seed is:\n\n" + seed \ + + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \ + + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" ) + dialog.set_title("Seed") + dialog.show() + dialog.run() + dialog.destroy() + +def restore_create_dialog(wallet): + + # ask if the user wants to create a new wallet, or recover from a seed. + # if he wants to recover, and nothing is found, do not create wallet + dialog = gtk.Dialog("electrum", parent=None, + flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, + buttons= ("create", 0, "restore",1, "cancel",2) ) + + label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?" ) + label.show() + dialog.vbox.pack_start(label) + dialog.show() + r = dialog.run() + dialog.destroy() + + if r==2: return False + + is_recovery = (r==1) + + # ask for the server. + if not run_network_dialog( wallet, parent=None ): return False + + if not is_recovery: + + wallet.new_seed(None) + # generate first key + wallet.init_mpk( wallet.seed ) + wallet.up_to_date_event.clear() + wallet.update() + + # run a dialog indicating the seed, ask the user to remember it + show_seed_dialog(wallet, None, None) + + #ask for password + change_password_dialog(wallet, None, None) + else: + # ask for seed and gap. + run_recovery_dialog( wallet ) + + dialog = gtk.MessageDialog( + parent = None, + flags = gtk.DIALOG_MODAL, + buttons = gtk.BUTTONS_CANCEL, + message_format = "Please wait..." ) + dialog.show() + + def recover_thread( wallet, dialog ): + wallet.init_mpk( wallet.seed ) # not encrypted at this point + wallet.up_to_date_event.clear() + wallet.update() + + if wallet.is_found(): + # history and addressbook + wallet.update_tx_history() + wallet.fill_addressbook() + print "recovery successful" + + gobject.idle_add( dialog.destroy ) + + thread.start_new_thread( recover_thread, ( wallet, dialog ) ) + r = dialog.run() + dialog.destroy() + if r==gtk.RESPONSE_CANCEL: return False + if not wallet.is_found: + show_message("No transactions found for this seed") + + wallet.save() + return True + + +def run_recovery_dialog(wallet): + message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet." + dialog = gtk.MessageDialog( + parent = None, + flags = gtk.DIALOG_MODAL, + buttons = gtk.BUTTONS_OK_CANCEL, + message_format = message) + + vbox = dialog.vbox + dialog.set_default_response(gtk.RESPONSE_OK) + + # ask seed, server and gap in the same dialog + seed_box = gtk.HBox() + seed_label = gtk.Label('Seed or mnemonic:') + seed_label.set_size_request(150,-1) + seed_box.pack_start(seed_label, False, False, 10) + seed_label.show() + seed_entry = gtk.Entry() + seed_entry.show() + seed_entry.set_size_request(450,-1) + seed_box.pack_start(seed_entry, False, False, 10) + add_help_button(seed_box, '.') + seed_box.show() + vbox.pack_start(seed_box, False, False, 5) + + gap = gtk.HBox() + gap_label = gtk.Label('Gap limit:') + gap_label.set_size_request(150,10) + gap_label.show() + gap.pack_start(gap_label,False, False, 10) + gap_entry = gtk.Entry() + gap_entry.set_text("%d"%wallet.gap_limit) + gap_entry.connect('changed', numbify, True) + gap_entry.show() + gap.pack_start(gap_entry,False,False, 10) + add_help_button(gap, 'The maximum gap that is allowed between unused addresses in your wallet. During wallet recovery, this parameter is used to decide when to stop the recovery process. If you increase this value, you will need to remember it in order to be able to recover your wallet from seed.') + gap.show() + vbox.pack_start(gap, False,False, 5) + + dialog.show() + r = dialog.run() + gap = gap_entry.get_text() + seed = seed_entry.get_text() + dialog.destroy() + + if r==gtk.RESPONSE_CANCEL: + sys.exit(1) + try: + gap = int(gap) + except: + show_message("error") + sys.exit(1) + + try: + seed.decode('hex') + except: + from electrum import mnemonic + print "not hex, trying decode" + seed = mnemonic.mn_decode( seed.split(' ') ) + if not seed: + show_message("no seed") + sys.exit(1) + + wallet.seed = seed + wallet.gap_limit = gap + wallet.save() + + + +def run_settings_dialog(wallet, parent): + + message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field." + + dialog = gtk.MessageDialog( + parent = parent, + flags = gtk.DIALOG_MODAL, + buttons = gtk.BUTTONS_OK_CANCEL, + message_format = message) + + image = gtk.Image() + image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG) + image.show() + dialog.set_image(image) + dialog.set_title("Settings") + + vbox = dialog.vbox + dialog.set_default_response(gtk.RESPONSE_OK) + + fee = gtk.HBox() + fee_entry = gtk.Entry() + fee_label = gtk.Label('Transaction fee:') + fee_label.set_size_request(150,10) + fee_label.show() + fee.pack_start(fee_label,False, False, 10) + fee_entry.set_text( str( Decimal(wallet.fee) /100000000 ) ) + fee_entry.connect('changed', numbify, False) + fee_entry.show() + fee.pack_start(fee_entry,False,False, 10) + add_help_button(fee, 'Fee per transaction input. Transactions involving multiple inputs tend to have a higher fee. Recommended value:0.0005') + fee.show() + vbox.pack_start(fee, False,False, 5) + + nz = gtk.HBox() + nz_entry = gtk.Entry() + nz_label = gtk.Label('Display zeros:') + nz_label.set_size_request(150,10) + nz_label.show() + nz.pack_start(nz_label,False, False, 10) + nz_entry.set_text( str( wallet.num_zeros )) + nz_entry.connect('changed', numbify, True) + nz_entry.show() + nz.pack_start(nz_entry,False,False, 10) + add_help_button(nz, "Number of zeros displayed after the decimal point.\nFor example, if this number is 2, then '5.' is displayed as '5.00'") + nz.show() + vbox.pack_start(nz, False,False, 5) + + dialog.show() + r = dialog.run() + fee = fee_entry.get_text() + nz = nz_entry.get_text() + + dialog.destroy() + if r==gtk.RESPONSE_CANCEL: + return + + try: + fee = int( 100000000 * Decimal(fee) ) + except: + show_message("error") + return + if wallet.fee != fee: + wallet.fee = fee + wallet.save() + + try: + nz = int( nz ) + if nz>8: nz = 8 + except: + show_message("error") + return + if wallet.num_zeros != nz: + wallet.num_zeros = nz + wallet.save() + + + + +def run_network_dialog( wallet, parent ): + image = gtk.Image() + image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG) + interface = wallet.interface + if parent: + if interface.is_connected: + status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks) + else: + status = "Not connected" + server = wallet.server + else: + import random + status = "Please choose a server." + server = random.choice( DEFAULT_SERVERS ) + + if not wallet.interface.servers: + servers_list = [] + for x in DEFAULT_SERVERS: + h,port,protocol = x.split(':') + servers_list.append( (h,[(protocol,port)] ) ) + else: + servers_list = wallet.interface.servers + + plist = {} + for item in servers_list: + host, pp = item + z = {} + for item2 in pp: + protocol, port = item2 + z[protocol] = port + plist[host] = z + + dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status) + dialog.set_title("Server") + dialog.set_image(image) + image.show() + + vbox = dialog.vbox + host_box = gtk.HBox() + host_label = gtk.Label('Connect to:') + host_label.set_size_request(100,-1) + host_label.show() + host_box.pack_start(host_label, False, False, 10) + host_entry = gtk.Entry() + host_entry.set_size_request(200,-1) + host_entry.set_text(server) + host_entry.show() + host_box.pack_start(host_entry, False, False, 10) + add_help_button(host_box, 'The name and port number of your Electrum server, separated by a colon. Example: "ecdsa.org:50000". If no port number is provided, port 50000 will be tried. Some servers allow you to connect through http (port 80) or https (port 443)') + host_box.show() + + + p_box = gtk.HBox(False, 10) + p_box.show() + + p_label = gtk.Label('Protocol:') + p_label.set_size_request(100,-1) + p_label.show() + p_box.pack_start(p_label, False, False, 10) + + radio1 = gtk.RadioButton(None, "tcp") + p_box.pack_start(radio1, True, True, 0) + radio1.show() + radio2 = gtk.RadioButton(radio1, "http") + p_box.pack_start(radio2, True, True, 0) + radio2.show() + + def current_line(): + return unicode(host_entry.get_text()).split(':') + + def set_button(protocol): + if protocol == 't': + radio1.set_active(1) + elif protocol == 'h': + radio2.set_active(1) + + def set_protocol(protocol): + host = current_line()[0] + pp = plist[host] + if protocol not in pp.keys(): + protocol = pp.keys()[0] + set_button(protocol) + port = pp[protocol] + host_entry.set_text( host + ':' + port + ':' + protocol) + + radio1.connect("toggled", lambda x,y:set_protocol('t'), "radio button 1") + radio2.connect("toggled", lambda x,y:set_protocol('h'), "radio button 1") + + server_list = gtk.ListStore(str) + for host in plist.keys(): + server_list.append([host]) + + treeview = gtk.TreeView(model=server_list) + treeview.show() + + if wallet.interface.servers: + label = 'Active Servers' + else: + label = 'Default Servers' + + tvcolumn = gtk.TreeViewColumn(label) + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, False) + tvcolumn.add_attribute(cell, 'text', 0) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroll.add(treeview) + scroll.show() + + vbox.pack_start(host_box, False,False, 5) + vbox.pack_start(p_box, True, True, 0) + vbox.pack_start(scroll) + + def my_treeview_cb(treeview): + path, view_column = treeview.get_cursor() + host = server_list.get_value( server_list.get_iter(path), 0) + + pp = plist[host] + if 't' in pp.keys(): + protocol = 't' + else: + protocol = pp.keys()[0] + port = pp[protocol] + host_entry.set_text( host + ':' + port + ':' + protocol) + set_button(protocol) + + treeview.connect('cursor-changed', my_treeview_cb) + + dialog.show() + r = dialog.run() + server = host_entry.get_text() + dialog.destroy() + + if r==gtk.RESPONSE_CANCEL: + return False + + try: + wallet.set_server(server) + except: + show_message("error:" + server) + return False + + if parent: + wallet.save() + return True + + + +def show_message(message, parent=None): + dialog = gtk.MessageDialog( + parent = parent, + flags = gtk.DIALOG_MODAL, + buttons = gtk.BUTTONS_CLOSE, + message_format = message ) + dialog.show() + dialog.run() + dialog.destroy() + +def password_line(label): + password = gtk.HBox() + password_label = gtk.Label(label) + password_label.set_size_request(120,10) + password_label.show() + password.pack_start(password_label,False, False, 10) + password_entry = gtk.Entry() + password_entry.set_size_request(300,-1) + password_entry.set_visibility(False) + password_entry.show() + password.pack_start(password_entry,False,False, 10) + password.show() + return password, password_entry + +def password_dialog(parent): + dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.") + dialog.get_image().set_visible(False) + current_pw, current_pw_entry = password_line('Password:') + current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK) + dialog.vbox.pack_start(current_pw, False, True, 0) + dialog.show() + result = dialog.run() + pw = current_pw_entry.get_text() + dialog.destroy() + if result != gtk.RESPONSE_CANCEL: return pw + +def change_password_dialog(wallet, parent, icon): + if parent: + msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' + else: + msg = "Please choose a password to encrypt your wallet keys" + + dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg) + dialog.set_title("Change password") + image = gtk.Image() + image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG) + image.show() + dialog.set_image(image) + + if wallet.use_encryption: + current_pw, current_pw_entry = password_line('Current password:') + dialog.vbox.pack_start(current_pw, False, True, 0) + + password, password_entry = password_line('New password:') + dialog.vbox.pack_start(password, False, True, 5) + password2, password2_entry = password_line('Confirm password:') + dialog.vbox.pack_start(password2, False, True, 5) + + dialog.show() + result = dialog.run() + password = current_pw_entry.get_text() if wallet.use_encryption else None + new_password = password_entry.get_text() + new_password2 = password2_entry.get_text() + dialog.destroy() + if result == gtk.RESPONSE_CANCEL: + return + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + show_message("Incorrect password") + return + + if new_password != new_password2: + show_message("passwords do not match") + return + + wallet.update_password(seed, new_password) + + if icon: + if wallet.use_encryption: + icon.set_tooltip_text('wallet is encrypted') + else: + icon.set_tooltip_text('wallet is unencrypted') + + +def add_help_button(hbox, message): + button = gtk.Button('?') + button.connect("clicked", lambda x: show_message(message)) + button.show() + hbox.pack_start(button,False, False) + + +class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) ) + +gobject.type_register(MyWindow) +gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W') +gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q') + + +class ElectrumWindow: + + def show_message(self, msg): + show_message(msg, self.window) + + def __init__(self, wallet): + self.wallet = wallet + self.funds_error = False # True if not enough funds + + self.window = MyWindow(gtk.WINDOW_TOPLEVEL) + self.window.set_title(APP_NAME + " " + self.wallet.electrum_version) + self.window.connect("destroy", gtk.main_quit) + self.window.set_border_width(0) + self.window.connect('mykeypress', gtk.main_quit) + self.window.set_default_size(720, 350) + + vbox = gtk.VBox() + + self.notebook = gtk.Notebook() + self.create_history_tab() + self.create_send_tab() + self.create_recv_tab() + self.create_book_tab() + self.create_about_tab() + self.notebook.show() + vbox.pack_start(self.notebook, True, True, 2) + + self.status_bar = gtk.Statusbar() + vbox.pack_start(self.status_bar, False, False, 0) + + self.status_image = gtk.Image() + self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) + self.status_image.set_alignment(True, 0.5 ) + self.status_image.show() + + self.network_button = gtk.Button() + self.network_button.connect("clicked", lambda x: run_network_dialog(self.wallet, self.window) ) + self.network_button.add(self.status_image) + self.network_button.set_relief(gtk.RELIEF_NONE) + self.network_button.show() + self.status_bar.pack_end(self.network_button, False, False) + + def seedb(w, wallet): + if wallet.use_encryption: + password = password_dialog(self.window) + if not password: return + else: password = None + show_seed_dialog(wallet, password, self.window) + button = gtk.Button('S') + button.connect("clicked", seedb, wallet ) + button.set_relief(gtk.RELIEF_NONE) + button.show() + self.status_bar.pack_end(button,False, False) + + settings_icon = gtk.Image() + settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + settings_icon.set_alignment(0.5, 0.5) + settings_icon.set_size_request(16,16 ) + settings_icon.show() + + prefs_button = gtk.Button() + prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) ) + prefs_button.add(settings_icon) + prefs_button.set_tooltip_text("Settings") + prefs_button.set_relief(gtk.RELIEF_NONE) + prefs_button.show() + self.status_bar.pack_end(prefs_button,False,False) + + pw_icon = gtk.Image() + pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) + pw_icon.set_alignment(0.5, 0.5) + pw_icon.set_size_request(16,16 ) + pw_icon.show() + + password_button = gtk.Button() + password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) + password_button.add(pw_icon) + password_button.set_relief(gtk.RELIEF_NONE) + password_button.show() + self.status_bar.pack_end(password_button,False,False) + + self.window.add(vbox) + self.window.show_all() + #self.fee_box.hide() + + self.context_id = self.status_bar.get_context_id("statusbar") + self.update_status_bar() + + def update_status_bar_thread(): + while True: + gobject.idle_add( self.update_status_bar ) + time.sleep(0.5) + + + def check_recipient_thread(): + old_r = '' + while True: + time.sleep(0.5) + if self.payto_entry.is_focus(): + continue + r = self.payto_entry.get_text() + if r != old_r: + old_r = r + r = r.strip() + if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r): + try: + to_address = self.wallet.get_alias(r, interactive=False) + except: + continue + if to_address: + s = r + ' <' + to_address + '>' + gobject.idle_add( lambda: self.payto_entry.set_text(s) ) + + + thread.start_new_thread(update_status_bar_thread, ()) + thread.start_new_thread(check_recipient_thread, ()) + self.notebook.set_current_page(0) + + + def add_tab(self, page, name): + tab_label = gtk.Label(name) + tab_label.show() + self.notebook.append_page(page, tab_label) + + + def create_send_tab(self): + + page = vbox = gtk.VBox() + page.show() + + payto = gtk.HBox() + payto_label = gtk.Label('Pay to:') + payto_label.set_size_request(100,-1) + payto.pack_start(payto_label, False) + payto_entry = gtk.Entry() + payto_entry.set_size_request(450, 26) + payto.pack_start(payto_entry, False) + vbox.pack_start(payto, False, False, 5) + + message = gtk.HBox() + message_label = gtk.Label('Description:') + message_label.set_size_request(100,-1) + message.pack_start(message_label, False) + message_entry = gtk.Entry() + message_entry.set_size_request(450, 26) + message.pack_start(message_entry, False) + vbox.pack_start(message, False, False, 5) + + amount_box = gtk.HBox() + amount_label = gtk.Label('Amount:') + amount_label.set_size_request(100,-1) + amount_box.pack_start(amount_label, False) + amount_entry = gtk.Entry() + amount_entry.set_size_request(120, -1) + amount_box.pack_start(amount_entry, False) + vbox.pack_start(amount_box, False, False, 5) + + self.fee_box = fee_box = gtk.HBox() + fee_label = gtk.Label('Fee:') + fee_label.set_size_request(100,-1) + fee_box.pack_start(fee_label, False) + fee_entry = gtk.Entry() + fee_entry.set_size_request(60, 26) + fee_box.pack_start(fee_entry, False) + vbox.pack_start(fee_box, False, False, 5) + + end_box = gtk.HBox() + empty_label = gtk.Label('') + empty_label.set_size_request(100,-1) + end_box.pack_start(empty_label, False) + send_button = gtk.Button("Send") + send_button.show() + end_box.pack_start(send_button, False, False, 0) + clear_button = gtk.Button("Clear") + clear_button.show() + end_box.pack_start(clear_button, False, False, 15) + send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry)) + clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry)) + + vbox.pack_start(end_box, False, False, 5) + + # display this line only if there is a signature + payto_sig = gtk.HBox() + payto_sig_id = gtk.Label('') + payto_sig.pack_start(payto_sig_id, False) + vbox.pack_start(payto_sig, True, True, 5) + + + self.user_fee = False + + def entry_changed( entry, is_fee ): + self.funds_error = False + amount = numbify(amount_entry) + fee = numbify(fee_entry) + if not is_fee: fee = None + if amount is None: + return + inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee ) + if not is_fee: + fee_entry.set_text( str( Decimal( fee ) / 100000000 ) ) + self.fee_box.show() + if inputs: + amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000")) + fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000")) + send_button.set_sensitive(True) + else: + send_button.set_sensitive(False) + amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000")) + fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000")) + self.funds_error = True + + amount_entry.connect('changed', entry_changed, False) + fee_entry.connect('changed', entry_changed, True) + + self.payto_entry = payto_entry + self.payto_fee_entry = fee_entry + self.payto_sig_id = payto_sig_id + self.payto_sig = payto_sig + self.amount_entry = amount_entry + self.message_entry = message_entry + self.add_tab(page, 'Send') + + def set_frozen(self,entry,frozen): + if frozen: + entry.set_editable(False) + entry.set_has_frame(False) + entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee")) + else: + entry.set_editable(True) + entry.set_has_frame(True) + entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff")) + + def set_url(self, url): + payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question) + self.notebook.set_current_page(1) + self.payto_entry.set_text(payto) + self.message_entry.set_text(message) + self.amount_entry.set_text(amount) + if identity: + self.set_frozen(self.payto_entry,True) + self.set_frozen(self.amount_entry,True) + self.set_frozen(self.message_entry,True) + self.payto_sig_id.set_text( ' The bitcoin URI was signed by ' + identity ) + else: + self.payto_sig.set_visible(False) + + def create_about_tab(self): + import pango + page = gtk.VBox() + page.show() + tv = gtk.TextView() + tv.set_editable(False) + tv.set_cursor_visible(False) + tv.modify_font(pango.FontDescription(MONOSPACE_FONT)) + page.pack_start(tv) + self.info = tv.get_buffer() + self.add_tab(page, 'Wall') + + def do_clear(self, w, data): + self.payto_sig.set_visible(False) + self.payto_fee_entry.set_text('') + for entry in [self.payto_entry,self.amount_entry,self.message_entry]: + self.set_frozen(entry,False) + entry.set_text('') + + def question(self,msg): + dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg) + dialog.show() + result = dialog.run() + dialog.destroy() + return result == gtk.RESPONSE_OK + + def do_send(self, w, data): + payto_entry, label_entry, amount_entry, fee_entry = data + label = label_entry.get_text() + r = payto_entry.get_text() + r = r.strip() + + m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r) + m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) + + if m1: + to_address = self.wallet.get_alias(r, True, self.show_message, self.question) + if not to_address: + return + else: + self.update_sending_tab() + + elif m2: + to_address = m2.group(5) + else: + to_address = r + + if not self.wallet.is_valid(to_address): + self.show_message( "invalid bitcoin address:\n"+to_address) + return + + try: + amount = int( Decimal(amount_entry.get_text()) * 100000000 ) + except: + self.show_message( "invalid amount") + return + try: + fee = int( Decimal(fee_entry.get_text()) * 100000000 ) + except: + self.show_message( "invalid fee") + return + + if self.wallet.use_encryption: + password = password_dialog(self.window) + if not password: + return + else: + password = None + + try: + tx = self.wallet.mktx( to_address, amount, label, password, fee ) + except BaseException, e: + self.show_message(e.message) + return + + status, msg = self.wallet.sendtx( tx ) + if status: + self.show_message( "payment sent.\n" + msg ) + payto_entry.set_text("") + label_entry.set_text("") + amount_entry.set_text("") + fee_entry.set_text("") + #self.fee_box.hide() + self.update_sending_tab() + else: + self.show_message( msg ) + + + def treeview_button_press(self, treeview, event): + if event.type == gtk.gdk._2BUTTON_PRESS: + c = treeview.get_cursor()[0] + if treeview == self.history_treeview: + tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) + self.show_message(tx_details) + elif treeview == self.contacts_treeview: + m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) + a = self.wallet.aliases.get(m) + if a: + if a[0] in self.wallet.authorities.keys(): + s = self.wallet.authorities.get(a[0]) + else: + s = "self-signed" + msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] + self.show_message(msg) + + + def treeview_key_press(self, treeview, event): + c = treeview.get_cursor()[0] + if event.keyval == gtk.keysyms.Up: + if c and c[0] == 0: + treeview.parent.grab_focus() + treeview.set_cursor((0,)) + elif event.keyval == gtk.keysyms.Return: + if treeview == self.history_treeview: + tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) + self.show_message(tx_details) + elif treeview == self.contacts_treeview: + m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) + a = self.wallet.aliases.get(m) + if a: + if a[0] in self.wallet.authorities.keys(): + s = self.wallet.authorities.get(a[0]) + else: + s = "self" + msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0] + self.show_message(msg) + + return False + + def create_history_tab(self): + + self.history_list = gtk.ListStore(str, str, str, str, 'gboolean', str, str, str, str) + treeview = gtk.TreeView(model=self.history_list) + self.history_treeview = treeview + treeview.set_tooltip_column(7) + treeview.show() + treeview.connect('key-press-event', self.treeview_key_press) + treeview.connect('button-press-event', self.treeview_button_press) + + tvcolumn = gtk.TreeViewColumn('') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererPixbuf() + tvcolumn.pack_start(cell, False) + tvcolumn.set_attributes(cell, stock_id=1) + + tvcolumn = gtk.TreeViewColumn('Date') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, False) + tvcolumn.add_attribute(cell, 'text', 2) + + tvcolumn = gtk.TreeViewColumn('Description') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + cell.set_property('foreground', 'grey') + cell.set_property('family', MONOSPACE_FONT) + cell.set_property('editable', True) + def edited_cb(cell, path, new_text, h_list): + tx = h_list.get_value( h_list.get_iter(path), 0) + self.wallet.labels[tx] = new_text + self.wallet.save() + self.update_history_tab() + cell.connect('edited', edited_cb, self.history_list) + def editing_started(cell, entry, path, h_list): + tx = h_list.get_value( h_list.get_iter(path), 0) + if not self.wallet.labels.get(tx): entry.set_text('') + cell.connect('editing-started', editing_started, self.history_list) + tvcolumn.set_expand(True) + tvcolumn.pack_start(cell, True) + tvcolumn.set_attributes(cell, text=3, foreground_set = 4) + + tvcolumn = gtk.TreeViewColumn('Amount') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + cell.set_alignment(1, 0.5) + cell.set_property('family', MONOSPACE_FONT) + tvcolumn.pack_start(cell, False) + tvcolumn.add_attribute(cell, 'text', 5) + + tvcolumn = gtk.TreeViewColumn('Balance') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + cell.set_alignment(1, 0.5) + cell.set_property('family', MONOSPACE_FONT) + tvcolumn.pack_start(cell, False) + tvcolumn.add_attribute(cell, 'text', 6) + + tvcolumn = gtk.TreeViewColumn('Tooltip') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, False) + tvcolumn.add_attribute(cell, 'text', 7) + tvcolumn.set_visible(False) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.add(treeview) + + self.add_tab(scroll, 'History') + self.update_history_tab() + + + def create_recv_tab(self): + self.recv_list = gtk.ListStore(str, str, str) + self.add_tab( self.make_address_list(True), 'Receive') + self.update_receiving_tab() + + def create_book_tab(self): + self.addressbook_list = gtk.ListStore(str, str, str) + self.add_tab( self.make_address_list(False), 'Contacts') + self.update_sending_tab() + + def make_address_list(self, is_recv): + liststore = self.recv_list if is_recv else self.addressbook_list + treeview = gtk.TreeView(model= liststore) + treeview.connect('key-press-event', self.treeview_key_press) + treeview.connect('button-press-event', self.treeview_button_press) + treeview.show() + if not is_recv: + self.contacts_treeview = treeview + + tvcolumn = gtk.TreeViewColumn('Address') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + cell.set_property('family', MONOSPACE_FONT) + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 0) + + tvcolumn = gtk.TreeViewColumn('Label') + tvcolumn.set_expand(True) + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + cell.set_property('editable', True) + def edited_cb2(cell, path, new_text, liststore): + address = liststore.get_value( liststore.get_iter(path), 0) + self.wallet.labels[address] = new_text + self.wallet.save() + self.wallet.update_tx_labels() + self.update_receiving_tab() + self.update_sending_tab() + self.update_history_tab() + cell.connect('edited', edited_cb2, liststore) + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 1) + + tvcolumn = gtk.TreeViewColumn('Tx') + treeview.append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 2) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroll.add(treeview) + + hbox = gtk.HBox() + if not is_recv: + button = gtk.Button("New") + button.connect("clicked", self.newaddress_dialog) + button.show() + hbox.pack_start(button,False) + + def showqrcode(w, treeview, liststore): + path, col = treeview.get_cursor() + if not path: return + address = liststore.get_value(liststore.get_iter(path), 0) + qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H) + qr.addData(address) + qr.make() + boxsize = 7 + size = qr.getModuleCount()*boxsize + def area_expose_cb(area, event): + style = area.get_style() + k = qr.getModuleCount() + for r in range(k): + for c in range(k): + gc = style.black_gc if qr.isDark(r, c) else style.white_gc + area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize) + area = gtk.DrawingArea() + area.set_size_request(size, size) + area.connect("expose-event", area_expose_cb) + area.show() + dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1)) + dialog.vbox.add(area) + dialog.run() + dialog.destroy() + + button = gtk.Button("QR") + button.connect("clicked", showqrcode, treeview, liststore) + button.show() + hbox.pack_start(button,False) + + button = gtk.Button("Copy to clipboard") + def copy2clipboard(w, treeview, liststore): + import platform + path, col = treeview.get_cursor() + if path: + address = liststore.get_value( liststore.get_iter(path), 0) + if platform.system() == 'Windows': + from Tkinter import Tk + r = Tk() + r.withdraw() + r.clipboard_clear() + r.clipboard_append( address ) + r.destroy() + else: + c = gtk.clipboard_get() + c.set_text( address ) + button.connect("clicked", copy2clipboard, treeview, liststore) + button.show() + hbox.pack_start(button,False) + + if not is_recv: + button = gtk.Button("Pay to") + def payto(w, treeview, liststore): + path, col = treeview.get_cursor() + if path: + address = liststore.get_value( liststore.get_iter(path), 0) + self.payto_entry.set_text( address ) + self.notebook.set_current_page(1) + self.amount_entry.grab_focus() + + button.connect("clicked", payto, treeview, liststore) + button.show() + hbox.pack_start(button,False) + + vbox = gtk.VBox() + vbox.pack_start(scroll,True) + vbox.pack_start(hbox, False) + return vbox + + def update_status_bar(self): + interface = self.wallet.interface + if self.funds_error: + text = "Not enough funds" + elif interface.is_connected: + self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks)) + if self.wallet.blocks == -1: + self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) + text = "Connecting..." + elif self.wallet.blocks == 0: + self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) + text = "Server not ready" + elif not self.wallet.up_to_date: + self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU) + text = "Synchronizing..." + else: + self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) + self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks)) + c, u = self.wallet.get_balance() + text = "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) ) + if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() ) + else: + self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) + self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(interface.host, self.wallet.blocks)) + text = "Not connected" + + self.status_bar.pop(self.context_id) + self.status_bar.push(self.context_id, text) + + if self.wallet.was_updated and self.wallet.up_to_date: + self.update_history_tab() + self.update_receiving_tab() + # addressbook too... + self.info.set_text( self.wallet.banner ) + self.wallet.was_updated = False + + + def update_receiving_tab(self): + self.recv_list.clear() + for address in self.wallet.all_addresses(): + if self.wallet.is_change(address):continue + label = self.wallet.labels.get(address) + n = 0 + h = self.wallet.history.get(address,[]) + for item in h: + if not item['is_input'] : n=n+1 + tx = "None" if n==0 else "%d"%n + self.recv_list.append((address, label, tx )) + + def update_sending_tab(self): + # detect addresses that are not mine in history, add them here... + self.addressbook_list.clear() + for alias, v in self.wallet.aliases.items(): + s, target = v + label = self.wallet.labels.get(alias) + self.addressbook_list.append((alias, label, '-')) + + for address in self.wallet.addressbook: + label = self.wallet.labels.get(address) + n = 0 + for item in self.wallet.tx_history.values(): + if address in item['outputs'] : n=n+1 + tx = "None" if n==0 else "%d"%n + self.addressbook_list.append((address, label, tx)) + + def update_history_tab(self): + cursor = self.history_treeview.get_cursor()[0] + self.history_list.clear() + balance = 0 + for tx in self.wallet.get_tx_history(): + tx_hash = tx['tx_hash'] + if tx['height']: + conf = self.wallet.blocks - tx['height'] + 1 + time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3] + conf_icon = gtk.STOCK_APPLY + else: + conf = 0 + time_str = 'pending' + conf_icon = gtk.STOCK_EXECUTE + v = tx['value'] + balance += v + label = self.wallet.labels.get(tx_hash) + is_default_label = (label == '') or (label is None) + if is_default_label: label = tx['default_label'] + tooltip = tx_hash + "\n%d confirmations"%conf + + # tx = self.wallet.tx_history.get(tx_hash) + details = "Transaction Details:\n\n" \ + + "Transaction ID:\n" + tx_hash + "\n\n" \ + + "Status: %d confirmations\n\n"%conf \ + + "Date: %s\n\n"%time_str \ + + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \ + + "Outputs:\n-"+ '\n-'.join(tx['outputs']) + r = self.wallet.receipts.get(tx_hash) + if r: + details += "\n_______________________________________" \ + + '\n\nSigned URI: ' + r[2] \ + + "\n\nSigned by: " + r[0] \ + + '\n\nSignature: ' + r[1] + + + self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label, + format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros), tooltip, details] ) + if cursor: self.history_treeview.set_cursor( cursor ) + + + + def newaddress_dialog(self, w): + + title = "New Contact" + dialog = gtk.Dialog(title, parent=self.window, + flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, + buttons= ("cancel", 0, "ok",1) ) + dialog.show() + + label = gtk.HBox() + label_label = gtk.Label('Label:') + label_label.set_size_request(120,10) + label_label.show() + label.pack_start(label_label) + label_entry = gtk.Entry() + label_entry.show() + label.pack_start(label_entry) + label.show() + dialog.vbox.pack_start(label, False, True, 5) + + address = gtk.HBox() + address_label = gtk.Label('Address:') + address_label.set_size_request(120,10) + address_label.show() + address.pack_start(address_label) + address_entry = gtk.Entry() + address_entry.show() + address.pack_start(address_entry) + address.show() + dialog.vbox.pack_start(address, False, True, 5) + + result = dialog.run() + address = address_entry.get_text() + label = label_entry.get_text() + dialog.destroy() + + if result == 1: + if self.wallet.is_valid(address): + self.wallet.addressbook.append(address) + if label: self.wallet.labels[address] = label + self.wallet.save() + self.update_sending_tab() + else: + errorDialog = gtk.MessageDialog( + parent=self.window, + flags=gtk.DIALOG_MODAL, + buttons= gtk.BUTTONS_CLOSE, + message_format = "Invalid address") + errorDialog.show() + errorDialog.run() + errorDialog.destroy() + + + +class ElectrumGui(): + + def __init__(self, wallet): + self.wallet = wallet + + def main(self, url=None): + ew = ElectrumWindow(self.wallet) + if url: ew.set_url(url) + gtk.main() + + def restore_or_create(self): + return restore_create_dialog(self.wallet) diff --git a/lib/gui_qt.py b/lib/gui_qt.py new file mode 100644 index 0000000..cd5ccd7 --- /dev/null +++ b/lib/gui_qt.py @@ -0,0 +1,1222 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2012 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys, time, datetime, re + +# todo: see PySide + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import PyQt4.QtCore as QtCore +import PyQt4.QtGui as QtGui +from interface import DEFAULT_SERVERS + +try: + import icons_rc +except: + print "Could not import icons_rp.py" + print "Please generate it with: 'pyrcc4 icons.qrc -o icons_rc.py'" + sys.exit(1) + +from wallet import format_satoshis +from decimal import Decimal + +import platform +MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace' + + +def numbify(entry, is_int = False): + text = unicode(entry.text()).strip() + chars = '0123456789' + if not is_int: chars +='.' + s = ''.join([i for i in text if i in chars]) + if not is_int: + if '.' in s: + p = s.find('.') + s = s.replace('.','') + s = s[:p] + '.' + s[p:p+8] + try: + amount = int( Decimal(s) * 100000000 ) + except: + amount = None + else: + try: + amount = int( s ) + except: + amount = None + entry.setText(s) + return amount + + +class Timer(QtCore.QThread): + def run(self): + while True: + self.emit(QtCore.SIGNAL('timersignal')) + time.sleep(0.5) + +class EnterButton(QPushButton): + def __init__(self, text, func): + QPushButton.__init__(self, text) + self.func = func + self.clicked.connect(func) + + def keyPressEvent(self, e): + if e.key() == QtCore.Qt.Key_Return: + apply(self.func,()) + +class StatusBarButton(QPushButton): + def __init__(self, icon, tooltip, func): + QPushButton.__init__(self, icon, '') + self.setToolTip(tooltip) + self.setFlat(True) + self.setMaximumWidth(25) + self.clicked.connect(func) + self.func = func + + def keyPressEvent(self, e): + if e.key() == QtCore.Qt.Key_Return: + apply(self.func,()) + + +class QRCodeWidget(QWidget): + + def __init__(self, addr): + super(QRCodeWidget, self).__init__() + self.setGeometry(300, 300, 350, 350) + self.set_addr(addr) + + def set_addr(self, addr): + from electrum import pyqrnative + self.addr = addr + self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L) + self.qr.addData(addr) + self.qr.make() + + def paintEvent(self, e): + qp = QtGui.QPainter() + qp.begin(self) + boxsize = 7 + size = self.qr.getModuleCount()*boxsize + k = self.qr.getModuleCount() + black = QColor(0, 0, 0, 255) + white = QColor(255, 255, 255, 255) + for r in range(k): + for c in range(k): + if self.qr.isDark(r, c): + qp.setBrush(black) + qp.setPen(black) + else: + qp.setBrush(white) + qp.setPen(white) + qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize) + qp.end() + + + +def ok_cancel_buttons(dialog): + hbox = QHBoxLayout() + hbox.addStretch(1) + b = QPushButton("OK") + hbox.addWidget(b) + b.clicked.connect(dialog.accept) + b = QPushButton("Cancel") + hbox.addWidget(b) + b.clicked.connect(dialog.reject) + return hbox + + +class ElectrumWindow(QMainWindow): + + def __init__(self, wallet): + QMainWindow.__init__(self) + self.wallet = wallet + self.wallet.gui_callback = self.update_callback + + self.funds_error = False + + self.tabs = tabs = QTabWidget(self) + tabs.addTab(self.create_history_tab(), 'History') + tabs.addTab(self.create_send_tab(), 'Send') + tabs.addTab(self.create_receive_tab(), 'Receive') + tabs.addTab(self.create_contacts_tab(),'Contacts') + tabs.addTab(self.create_wall_tab(), 'Wall') + tabs.setMinimumSize(600, 400) + tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.setCentralWidget(tabs) + self.create_status_bar() + self.setGeometry(100,100,840,400) + self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version ) + self.show() + + QShortcut(QKeySequence("Ctrl+W"), self, self.close) + QShortcut(QKeySequence("Ctrl+Q"), self, self.close) + QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() )) + QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() )) + + self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet) + + + def connect_slots(self, sender): + self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient) + self.previous_payto_e='' + + def check_recipient(self): + if self.payto_e.hasFocus(): + return + r = unicode( self.payto_e.text() ) + if r != self.previous_payto_e: + self.previous_payto_e = r + r = r.strip() + if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r): + try: + to_address = self.wallet.get_alias(r, True, self.show_message, self.question) + except: + return + if to_address: + s = r + ' <' + to_address + '>' + self.payto_e.setText(s) + + + def update_callback(self): + self.emit(QtCore.SIGNAL('updatesignal')) + + def update_wallet(self): + if self.wallet.interface.is_connected: + if self.wallet.blocks == -1: + text = "Connecting..." + icon = QIcon(":icons/status_disconnected.png") + elif self.wallet.blocks == 0: + text = "Server not ready" + icon = QIcon(":icons/status_disconnected.png") + elif not self.wallet.up_to_date: + text = "Synchronizing..." + icon = QIcon(":icons/status_waiting.png") + else: + c, u = self.wallet.get_balance() + text = "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) ) + if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() ) + icon = QIcon(":icons/status_connected.png") + else: + text = "Not connected" + icon = QIcon(":icons/status_disconnected.png") + + if self.funds_error: + text = "Not enough funds" + + self.statusBar().showMessage(text) + self.status_button.setIcon( icon ) + + if self.wallet.up_to_date: + self.textbox.setText( self.wallet.banner ) + self.update_history_tab() + self.update_receive_tab() + self.update_contacts_tab() + + + def create_history_tab(self): + self.history_list = w = QTreeWidget(self) + #print w.getContentsMargins() + w.setColumnCount(5) + w.setColumnWidth(0, 40) + w.setColumnWidth(1, 140) + w.setColumnWidth(2, 350) + w.setColumnWidth(3, 140) + w.setColumnWidth(4, 140) + w.setHeaderLabels( [ '', 'Date', 'Description', 'Amount', 'Balance'] ) + self.connect(w, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.tx_details) + self.connect(w, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked) + self.connect(w, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed) + return w + + def tx_details(self, item, column): + tx_hash = str(item.toolTip(0)) + tx = self.wallet.tx_history.get(tx_hash) + + if tx['height']: + conf = self.wallet.blocks - tx['height'] + 1 + time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3] + else: + conf = 0 + time_str = 'pending' + + tx_details = "Transaction Details:\n\n" \ + + "Transaction ID:\n" + tx_hash + "\n\n" \ + + "Status: %d confirmations\n\n"%conf \ + + "Date: %s\n\n"%time_str \ + + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \ + + "Outputs:\n-"+ '\n-'.join(tx['outputs']) + + r = self.wallet.receipts.get(tx_hash) + if r: + tx_details += "\n_______________________________________" \ + + '\n\nSigned URI: ' + r[2] \ + + "\n\nSigned by: " + r[0] \ + + '\n\nSignature: ' + r[1] + + QMessageBox.information(self, 'Details', tx_details, 'OK') + + + def tx_label_clicked(self, item, column): + if column==2 and item.isSelected(): + tx_hash = str(item.toolTip(0)) + self.is_edit=True + #if not self.wallet.labels.get(tx_hash): item.setText(2,'') + item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) + self.history_list.editItem( item, column ) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) + self.is_edit=False + + def tx_label_changed(self, item, column): + if self.is_edit: + return + self.is_edit=True + tx_hash = str(item.toolTip(0)) + tx = self.wallet.tx_history.get(tx_hash) + s = self.wallet.labels.get(tx_hash) + text = unicode( item.text(2) ) + if text: + self.wallet.labels[tx_hash] = text + item.setForeground(2, QBrush(QColor('black'))) + else: + if s: self.wallet.labels.pop(tx_hash) + text = tx['default_label'] + item.setText(2, text) + item.setForeground(2, QBrush(QColor('gray'))) + self.is_edit=False + + def address_label_clicked(self, item, column, l): + if column==1 and item.isSelected(): + addr = unicode( item.text(0) ) + if addr in map(lambda x:x[1], self.wallet.aliases.values()): + return + item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) + l.editItem( item, column ) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) + + def address_label_changed(self, item, column, l): + addr = unicode( item.text(0) ) + text = unicode( item.text(1) ) + if text: + self.wallet.labels[addr] = text + else: + s = self.wallet.labels.get(addr) + if s: self.wallet.labels.pop(addr) + self.update_history_tab() + + def update_history_tab(self): + self.history_list.clear() + balance = 0 + for tx in self.wallet.get_tx_history(): + tx_hash = tx['tx_hash'] + if tx['height']: + conf = self.wallet.blocks - tx['height'] + 1 + time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3] + icon = QIcon(":icons/confirmed.png") + else: + conf = 0 + time_str = 'pending' + icon = QIcon(":icons/unconfirmed.png") + v = tx['value'] + balance += v + label = self.wallet.labels.get(tx_hash) + is_default_label = (label == '') or (label is None) + if is_default_label: label = tx['default_label'] + + item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] ) + item.setFont(2, QFont(MONOSPACE_FONT)) + item.setFont(3, QFont(MONOSPACE_FONT)) + item.setFont(4, QFont(MONOSPACE_FONT)) + item.setToolTip(0, tx_hash) + if is_default_label: + item.setForeground(2, QBrush(QColor('grey'))) + + item.setIcon(0, icon) + self.history_list.insertTopLevelItem(0,item) + + + def create_send_tab(self): + w = QWidget() + + grid = QGridLayout() + grid.setSpacing(8) + grid.setColumnMinimumWidth(3,300) + grid.setColumnStretch(4,1) + + self.payto_e = QLineEdit() + grid.addWidget(QLabel('Pay to'), 1, 0) + grid.addWidget(self.payto_e, 1, 1, 1, 3) + + self.message_e = QLineEdit() + grid.addWidget(QLabel('Description'), 2, 0) + grid.addWidget(self.message_e, 2, 1, 1, 3) + + self.amount_e = QLineEdit() + grid.addWidget(QLabel('Amount'), 3, 0) + grid.addWidget(self.amount_e, 3, 1, 1, 2) + + self.fee_e = QLineEdit() + grid.addWidget(QLabel('Fee'), 4, 0) + grid.addWidget(self.fee_e, 4, 1, 1, 2) + + b = EnterButton("Send", self.do_send) + grid.addWidget(b, 5, 1) + + b = EnterButton("Clear",self.do_clear) + grid.addWidget(b, 5, 2) + + self.payto_sig = QLabel('') + grid.addWidget(self.payto_sig, 6, 0, 1, 4) + + w.setLayout(grid) + w.show() + + w2 = QWidget() + vbox = QVBoxLayout() + vbox.addWidget(w) + vbox.addStretch(1) + w2.setLayout(vbox) + + def entry_changed( is_fee ): + self.funds_error = False + amount = numbify(self.amount_e) + fee = numbify(self.fee_e) + if not is_fee: fee = None + if amount is None: + return + inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee ) + if not is_fee: + self.fee_e.setText( str( Decimal( fee ) / 100000000 ) ) + if inputs: + palette = QPalette() + palette.setColor(self.amount_e.foregroundRole(), QColor('black')) + else: + palette = QPalette() + palette.setColor(self.amount_e.foregroundRole(), QColor('red')) + self.funds_error = True + self.amount_e.setPalette(palette) + self.fee_e.setPalette(palette) + + self.amount_e.textChanged.connect(lambda: entry_changed(False) ) + self.fee_e.textChanged.connect(lambda: entry_changed(True) ) + + return w2 + + def do_send(self): + + label = unicode( self.message_e.text() ) + r = unicode( self.payto_e.text() ) + r = r.strip() + + m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r) + m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) + + if m1: + to_address = self.wallet.get_alias(r, True, self.show_message, self.question) + if not to_address: + return + elif m2: + to_address = m2.group(5) + else: + to_address = r + + if not self.wallet.is_valid(to_address): + QMessageBox.warning(self, 'Error', 'Invalid Bitcoin Address:\n'+to_address, 'OK') + return + + try: + amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 ) + except: + QMessageBox.warning(self, 'Error', 'Invalid Amount', 'OK') + return + try: + fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 ) + except: + QMessageBox.warning(self, 'Error', 'Invalid Fee', 'OK') + return + + if self.wallet.use_encryption: + password = self.password_dialog() + if not password: + return + else: + password = None + + try: + tx = self.wallet.mktx( to_address, amount, label, password, fee ) + except BaseException, e: + self.show_message(e.message) + return + + status, msg = self.wallet.sendtx( tx ) + if status: + QMessageBox.information(self, '', 'Payment sent.\n'+msg, 'OK') + self.do_clear() + self.update_contacts_tab() + else: + QMessageBox.warning(self, 'Error', msg, 'OK') + + + def set_url(self, url): + payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question) + self.tabs.setCurrentIndex(1) + self.payto_e.setText(payto) + self.message_e.setText(message) + self.amount_e.setText(amount) + if identity: + self.set_frozen(self.payto_e,True) + self.set_frozen(self.amount_e,True) + self.set_frozen(self.message_e,True) + self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity ) + else: + self.payto_sig.setVisible(False) + + def do_clear(self): + self.payto_sig.setVisible(False) + for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]: + e.setText('') + self.set_frozen(e,False) + + def set_frozen(self,entry,frozen): + if frozen: + entry.setReadOnly(True) + entry.setFrame(False) + palette = QPalette() + palette.setColor(entry.backgroundRole(), QColor('lightgray')) + entry.setPalette(palette) + else: + entry.setReadOnly(False) + entry.setFrame(True) + palette = QPalette() + palette.setColor(entry.backgroundRole(), QColor('white')) + entry.setPalette(palette) + + + + + def clear_buttons(self, hbox): + while hbox.count(): hbox.removeItem(hbox.itemAt(0)) + + def add_buttons(self, l, hbox, is_recv): + self.clear_buttons(hbox) + + i = l.currentItem() + if not i: return + addr = unicode( i.text(0) ) + + hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(addr))) + hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(addr))) + if is_recv: + def toggle_freeze(addr): + if addr in self.wallet.frozen_addresses: + self.wallet.frozen_addresses.remove(addr) + else: + self.wallet.frozen_addresses.append(addr) + self.wallet.save() + self.update_receive_tab() + + t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze" + hbox.addWidget(EnterButton(t, lambda: toggle_freeze(addr))) + + else: + def payto(addr): + if not addr:return + self.tabs.setCurrentIndex(1) + self.payto_e.setText(addr) + self.amount_e.setFocus() + hbox.addWidget(EnterButton('Pay to', lambda: payto(addr))) + hbox.addWidget(EnterButton("New", self.newaddress_dialog)) + hbox.addStretch(1) + + + def create_receive_tab(self): + l = QTreeWidget(self) + l.setColumnCount(4) + l.setColumnWidth(0, 350) + l.setColumnWidth(1, 330) + l.setColumnWidth(2, 100) + l.setColumnWidth(3, 10) + l.setHeaderLabels( ['Address', 'Label','Balance','Tx']) + + w = QWidget() + vbox = QVBoxLayout() + w.setLayout(vbox) + + vbox.setMargin(0) + vbox.setSpacing(0) + vbox.addWidget(l) + buttons = QWidget() + vbox.addWidget(buttons) + + hbox = QHBoxLayout() + hbox.setMargin(0) + hbox.setSpacing(0) + buttons.setLayout(hbox) + + self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) + self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) + self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, True)) + self.receive_list = l + self.receive_buttons_hbox = hbox + return w + + def create_contacts_tab(self): + l = QTreeWidget(self) + l.setColumnCount(3) + l.setColumnWidth(0, 350) + l.setColumnWidth(1, 330) + l.setColumnWidth(2, 20) + l.setHeaderLabels( ['Address', 'Label','Tx']) + + w = QWidget() + vbox = QVBoxLayout() + w.setLayout(vbox) + + vbox.setMargin(0) + vbox.setSpacing(0) + vbox.addWidget(l) + buttons = QWidget() + vbox.addWidget(buttons) + + hbox = QHBoxLayout() + hbox.setMargin(0) + hbox.setSpacing(0) + buttons.setLayout(hbox) + + self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) + self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) + self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details) + self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, False)) + + self.contacts_list = l + self.contacts_buttons_hbox = hbox + return w + + def update_receive_tab(self): + self.receive_list.clear() + self.clear_buttons(self.receive_buttons_hbox) + + for address in self.wallet.all_addresses(): + if self.wallet.is_change(address):continue + label = self.wallet.labels.get(address,'') + n = 0 + h = self.wallet.history.get(address,[]) + for item in h: + if not item['is_input'] : n=n+1 + tx = "None" if n==0 else "%d"%n + + c, u = self.wallet.get_addr_balance(address) + balance = format_satoshis( c + u, False, self.wallet.num_zeros ) + if address in self.wallet.frozen_addresses: + balance += '[F]' + + item = QTreeWidgetItem( [ address, label, balance, tx] ) + item.setFont(0, QFont(MONOSPACE_FONT)) + self.receive_list.addTopLevelItem(item) + + def show_contact_details(self, item, column): + m = unicode(item.text(0)) + a = self.wallet.aliases.get(m) + if a: + if a[0] in self.wallet.authorities.keys(): + s = self.wallet.authorities.get(a[0]) + else: + s = "self-signed" + msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] + QMessageBox.information(self, 'Alias', msg, 'OK') + + def update_contacts_tab(self): + self.contacts_list.clear() + self.clear_buttons(self.contacts_buttons_hbox) + + for alias, v in self.wallet.aliases.items(): + s, target = v + item = QTreeWidgetItem( [ target, alias, '-'] ) + self.contacts_list.addTopLevelItem(item) + + for address in self.wallet.addressbook: + label = self.wallet.labels.get(address,'') + n = 0 + for item in self.wallet.tx_history.values(): + if address in item['outputs'] : n=n+1 + tx = "None" if n==0 else "%d"%n + item = QTreeWidgetItem( [ address, label, tx] ) + item.setFont(0, QFont(MONOSPACE_FONT)) + self.contacts_list.addTopLevelItem(item) + + + def create_wall_tab(self): + self.textbox = textbox = QTextEdit(self) + textbox.setFont(QFont(MONOSPACE_FONT)) + textbox.setReadOnly(True) + return textbox + + def create_status_bar(self): + sb = QStatusBar() + sb.setFixedHeight(35) + sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) ) + sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) ) + sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) ) + self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) + sb.addPermanentWidget( self.status_button ) + self.setStatusBar(sb) + + def newaddress_dialog(self): + text, ok = QInputDialog.getText(self, 'New Contact', 'Address:') + address = unicode(text) + if ok: + if self.wallet.is_valid(address): + self.wallet.addressbook.append(address) + self.wallet.save() + self.update_contacts_tab() + else: + QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK') + + @staticmethod + def show_seed_dialog(wallet, parent=None): + from electrum import mnemonic + if wallet.use_encryption: + password = parent.password_dialog() + if not password: return + else: + password = None + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + QMessageBox.warning(parent, 'Error', 'Invalid Password', 'OK') + return + + msg = "Your wallet generation seed is:\n\n" + seed \ + + "\n\nPlease keep it in a safe place; if you lose it,\nyou will not be able to restore your wallet.\n\n" \ + + "Equivalently, your wallet seed can be stored and\nrecovered with the following mnemonic code:\n\n\"" \ + + ' '.join(mnemonic.mn_encode(seed)) + "\"\n\n\n" + + d = QDialog(None) + d.setModal(1) + d.setWindowTitle("Seed") + d.setMinimumSize(400, 270) + + vbox = QVBoxLayout() + hbox = QHBoxLayout() + vbox2 = QVBoxLayout() + l = QLabel() + l.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56)) + vbox2.addWidget(l) + vbox2.addStretch(1) + hbox.addLayout(vbox2) + hbox.addWidget(QLabel(msg)) + vbox.addLayout(hbox) + + hbox = QHBoxLayout() + hbox.addStretch(1) + + + if parent: + app = parent.app + else: + app = QApplication + + b = QPushButton("Copy to Clipboard") + b.clicked.connect(lambda: app.clipboard().setText(' '.join(mnemonic.mn_encode(seed)))) + hbox.addWidget(b) + b = QPushButton("View as QR Code") + b.clicked.connect(lambda: ElectrumWindow.show_seed_qrcode(seed)) + hbox.addWidget(b) + + b = QPushButton("OK") + b.clicked.connect(d.accept) + hbox.addWidget(b) + vbox.addLayout(hbox) + d.setLayout(vbox) + d.exec_() + + @staticmethod + def show_seed_qrcode(seed): + if not seed: return + d = QDialog(None) + d.setModal(1) + d.setWindowTitle("Seed") + d.setMinimumSize(270, 300) + vbox = QVBoxLayout() + vbox.addWidget(QRCodeWidget(seed)) + hbox = QHBoxLayout() + hbox.addStretch(1) + b = QPushButton("OK") + hbox.addWidget(b) + b.clicked.connect(d.accept) + + vbox.addLayout(hbox) + d.setLayout(vbox) + d.exec_() + + def show_address_qrcode(self,address): + if not address: return + d = QDialog(None) + d.setModal(1) + d.setWindowTitle(address) + d.setMinimumSize(270, 350) + vbox = QVBoxLayout() + qrw = QRCodeWidget(address) + vbox.addWidget(qrw) + + hbox = QHBoxLayout() + amount_e = QLineEdit() + hbox.addWidget(QLabel('Amount')) + hbox.addWidget(amount_e) + vbox.addLayout(hbox) + + #hbox = QHBoxLayout() + #label_e = QLineEdit() + #hbox.addWidget(QLabel('Label')) + #hbox.addWidget(label_e) + #vbox.addLayout(hbox) + + def amount_changed(): + amount = numbify(amount_e) + #label = str( label_e.getText() ) + if amount is not None: + qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000))) + else: + qrw.set_addr( address ) + qrw.repaint() + + def do_save(): + from electrum import bmp + bmp.save_qrcode(qrw.qr, "qrcode.bmp") + self.show_message("QR code saved to file 'qrcode.bmp'") + + amount_e.textChanged.connect( amount_changed ) + + hbox = QHBoxLayout() + hbox.addStretch(1) + b = QPushButton("Save") + b.clicked.connect(do_save) + hbox.addWidget(b) + b = QPushButton("Close") + hbox.addWidget(b) + b.clicked.connect(d.accept) + + vbox.addLayout(hbox) + d.setLayout(vbox) + d.exec_() + + def question(self, msg): + return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes + + def show_message(self, msg): + QMessageBox.information(self, 'Message', msg, 'OK') + + def password_dialog(self ): + d = QDialog(self) + d.setModal(1) + + pw = QLineEdit() + pw.setEchoMode(2) + + vbox = QVBoxLayout() + msg = 'Please enter your password' + vbox.addWidget(QLabel(msg)) + + grid = QGridLayout() + grid.setSpacing(8) + grid.addWidget(QLabel('Password'), 1, 0) + grid.addWidget(pw, 1, 1) + vbox.addLayout(grid) + + vbox.addLayout(ok_cancel_buttons(d)) + d.setLayout(vbox) + + if not d.exec_(): return + return unicode(pw.text()) + + @staticmethod + def change_password_dialog( wallet, parent=None ): + d = QDialog(parent) + d.setModal(1) + + pw = QLineEdit() + pw.setEchoMode(2) + new_pw = QLineEdit() + new_pw.setEchoMode(2) + conf_pw = QLineEdit() + conf_pw.setEchoMode(2) + + vbox = QVBoxLayout() + if parent: + msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' + else: + msg = "Please choose a password to encrypt your wallet keys.\nLeave these fields empty if you want to disable encryption." + vbox.addWidget(QLabel(msg)) + + grid = QGridLayout() + grid.setSpacing(8) + + if wallet.use_encryption: + grid.addWidget(QLabel('Password'), 1, 0) + grid.addWidget(pw, 1, 1) + + grid.addWidget(QLabel('New Password'), 2, 0) + grid.addWidget(new_pw, 2, 1) + + grid.addWidget(QLabel('Confirm Password'), 3, 0) + grid.addWidget(conf_pw, 3, 1) + vbox.addLayout(grid) + + vbox.addLayout(ok_cancel_buttons(d)) + d.setLayout(vbox) + + if not d.exec_(): return + + password = unicode(pw.text()) if wallet.use_encryption else None + new_password = unicode(new_pw.text()) + new_password2 = unicode(conf_pw.text()) + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + QMessageBox.warning(parent, 'Error', 'Incorrect Password', 'OK') + return + + if new_password != new_password2: + QMessageBox.warning(parent, 'Error', 'Passwords do not match', 'OK') + return + + wallet.update_password(seed, new_password) + + @staticmethod + def seed_dialog(wallet, parent=None): + d = QDialog(parent) + d.setModal(1) + + vbox = QVBoxLayout() + msg = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet." + vbox.addWidget(QLabel(msg)) + + grid = QGridLayout() + grid.setSpacing(8) + + seed_e = QLineEdit() + grid.addWidget(QLabel('Seed or mnemonic'), 1, 0) + grid.addWidget(seed_e, 1, 1) + + gap_e = QLineEdit() + gap_e.setText("5") + grid.addWidget(QLabel('Gap limit'), 2, 0) + grid.addWidget(gap_e, 2, 1) + gap_e.textChanged.connect(lambda: numbify(gap_e,True)) + vbox.addLayout(grid) + + vbox.addLayout(ok_cancel_buttons(d)) + d.setLayout(vbox) + + if not d.exec_(): return + + try: + gap = int(unicode(gap_e.text())) + except: + QMessageBox.warning(None, 'Error', 'error', 'OK') + sys.exit(0) + + try: + seed = unicode(seed_e.text()) + seed.decode('hex') + except: + from electrum import mnemonic + print "not hex, trying decode" + try: + seed = mnemonic.mn_decode( seed.split(' ') ) + except: + QMessageBox.warning(None, 'Error', 'I cannot decode this', 'OK') + sys.exit(0) + if not seed: + QMessageBox.warning(None, 'Error', 'no seed', 'OK') + sys.exit(0) + + wallet.seed = str(seed) + #print repr(wallet.seed) + wallet.gap_limit = gap + return True + + + def settings_dialog(self): + d = QDialog(self) + d.setModal(1) + + vbox = QVBoxLayout() + + msg = 'Here are the settings of your wallet.' + vbox.addWidget(QLabel(msg)) + + grid = QGridLayout() + grid.setSpacing(8) + vbox.addLayout(grid) + + fee_e = QLineEdit() + fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) ) + grid.addWidget(QLabel('Fee per tx. input'), 2, 0) + grid.addWidget(fee_e, 2, 1) + fee_e.textChanged.connect(lambda: numbify(fee_e,False)) + + nz_e = QLineEdit() + nz_e.setText("%d"% self.wallet.num_zeros) + grid.addWidget(QLabel('Zeros displayed after decimal point'), 3, 0) + grid.addWidget(nz_e, 3, 1) + nz_e.textChanged.connect(lambda: numbify(nz_e,True)) + + vbox.addLayout(ok_cancel_buttons(d)) + d.setLayout(vbox) + + if not d.exec_(): return + + fee = unicode(fee_e.text()) + try: + fee = int( 100000000 * Decimal(fee) ) + except: + QMessageBox.warning(self, 'Error', 'Invalid value:%s'%fee, 'OK') + return + + if self.wallet.fee != fee: + self.wallet.fee = fee + self.wallet.save() + + nz = unicode(nz_e.text()) + try: + nz = int( nz ) + if nz>8: nz=8 + except: + QMessageBox.warning(self, 'Error', 'Invalid value:%s'%nz, 'OK') + return + + if self.wallet.num_zeros != nz: + self.wallet.num_zeros = nz + self.update_history_tab() + self.update_receive_tab() + self.wallet.save() + + @staticmethod + def network_dialog(wallet, parent=None): + interface = wallet.interface + if parent: + if interface.is_connected: + status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks) + else: + status = "Not connected" + server = wallet.server + else: + import random + status = "Please choose a server." + server = random.choice( DEFAULT_SERVERS ) + + if not wallet.interface.servers: + servers_list = [] + for x in DEFAULT_SERVERS: + h,port,protocol = x.split(':') + servers_list.append( (h,[(protocol,port)] ) ) + else: + servers_list = wallet.interface.servers + + plist = {} + for item in servers_list: + host, pp = item + z = {} + for item2 in pp: + protocol, port = item2 + z[protocol] = port + plist[host] = z + + d = QDialog(parent) + d.setModal(1) + d.setWindowTitle('Server') + d.setMinimumSize(375, 20) + + vbox = QVBoxLayout() + vbox.setSpacing(20) + + hbox = QHBoxLayout() + l = QLabel() + l.setPixmap(QPixmap(":icons/network.png")) + hbox.addWidget(l) + hbox.addWidget(QLabel(status)) + + vbox.addLayout(hbox) + + hbox = QHBoxLayout() + host_line = QLineEdit() + host_line.setText(server) + hbox.addWidget(QLabel('Connect to:')) + hbox.addWidget(host_line) + vbox.addLayout(hbox) + + hbox = QHBoxLayout() + + buttonGroup = QGroupBox("protocol") + radio1 = QRadioButton("tcp", buttonGroup) + radio2 = QRadioButton("http", buttonGroup) + + def current_line(): + return unicode(host_line.text()).split(':') + + def set_button(protocol): + if protocol == 't': + radio1.setChecked(1) + elif protocol == 'h': + radio2.setChecked(1) + + def set_protocol(protocol): + host = current_line()[0] + pp = plist[host] + if protocol not in pp.keys(): + protocol = pp.keys()[0] + set_button(protocol) + port = pp[protocol] + host_line.setText( host + ':' + port + ':' + protocol) + + radio1.clicked.connect(lambda x: set_protocol('t') ) + radio2.clicked.connect(lambda x: set_protocol('h') ) + + set_button(current_line()[2]) + + hbox.addWidget(QLabel('Protocol:')) + hbox.addWidget(radio1) + hbox.addWidget(radio2) + + vbox.addLayout(hbox) + + if wallet.interface.servers: + label = 'Active Servers' + else: + label = 'Default Servers' + + servers_list_widget = QTreeWidget(parent) + servers_list_widget.setHeaderLabels( [ label ] ) + servers_list_widget.setMaximumHeight(150) + for host in plist.keys(): + servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] )) + + def do_set_line(x): + host = unicode(x.text(0)) + pp = plist[host] + if 't' in pp.keys(): + protocol = 't' + else: + protocol = pp.keys()[0] + port = pp[protocol] + host_line.setText( host + ':' + port + ':' + protocol) + set_button(protocol) + + servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line) + vbox.addWidget(servers_list_widget) + + vbox.addLayout(ok_cancel_buttons(d)) + d.setLayout(vbox) + + if not d.exec_(): return + server = unicode( host_line.text() ) + + try: + wallet.set_server(server) + except: + QMessageBox.information(None, 'Error', 'error', 'OK') + if parent == None: + sys.exit(1) + else: + return + + return True + + + +class ElectrumGui(): + + def __init__(self, wallet): + self.wallet = wallet + self.app = QApplication(sys.argv) + + def waiting_dialog(self): + + s = Timer() + s.start() + w = QDialog() + w.resize(200, 70) + w.setWindowTitle('Electrum') + l = QLabel('') + vbox = QVBoxLayout() + vbox.addWidget(l) + w.setLayout(vbox) + w.show() + def f(): + if self.wallet.up_to_date: w.close() + else: + l.setText("Please wait...\nGenerating addresses: %d"%len(self.wallet.all_addresses())) + pass + w.connect(s, QtCore.SIGNAL('timersignal'), f) + self.wallet.interface.poke() + w.exec_() + w.destroy() + + + def restore_or_create(self): + + msg = "Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?" + r = QMessageBox.question(None, 'Message', msg, 'create', 'restore', 'cancel', 0, 2) + if r==2: return False + + is_recovery = (r==1) + wallet = self.wallet + # ask for the server. + if not ElectrumWindow.network_dialog( wallet, parent=None ): return False + + if not is_recovery: + wallet.new_seed(None) + wallet.init_mpk( wallet.seed ) + wallet.up_to_date_event.clear() + wallet.up_to_date = False + self.waiting_dialog() + # run a dialog indicating the seed, ask the user to remember it + ElectrumWindow.show_seed_dialog(wallet) + #ask for password + ElectrumWindow.change_password_dialog(wallet) + else: + # ask for seed and gap. + if not ElectrumWindow.seed_dialog( wallet ): return False + wallet.init_mpk( wallet.seed ) + wallet.up_to_date_event.clear() + wallet.up_to_date = False + self.waiting_dialog() + if wallet.is_found(): + # history and addressbook + wallet.update_tx_history() + wallet.fill_addressbook() + print "recovery successful" + wallet.save() + else: + QMessageBox.information(None, 'Message', "No transactions found for this seed", 'OK') + + wallet.save() + return True + + def main(self,url): + s = Timer() + s.start() + w = ElectrumWindow(self.wallet) + if url: w.set_url(url) + w.app = self.app + w.connect_slots(s) + w.update_wallet() + + self.app.exec_() diff --git a/lib/interface.py b/lib/interface.py new file mode 100644 index 0000000..0ace32b --- /dev/null +++ b/lib/interface.py @@ -0,0 +1,393 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import random, socket, ast, re +import threading, traceback, sys, time, json, Queue + +from version import ELECTRUM_VERSION + +DEFAULT_TIMEOUT = 5 +DEFAULT_SERVERS = [ 'ecdsa.org:50001:t', 'electrum.novit.ro:50001:t', 'electrum.bitcoins.sk:50001:t'] # list of default servers + + +def old_to_new(s): + s = s.replace("'blk_hash'", "'block_hash'") + s = s.replace("'pos'", "'index'") + s = s.replace("'nTime'", "'timestamp'") + s = s.replace("'is_in'", "'is_input'") + s = s.replace("'raw_scriptPubKey'","'raw_output_script'") + return s + + +class Interface(threading.Thread): + def __init__(self, host, port): + threading.Thread.__init__(self) + self.daemon = True + self.host = host + self.port = port + + self.servers = [] # actual list from IRC + self.rtime = 0 + + self.is_connected = True + self.poll_interval = 1 + + #json + self.message_id = 0 + self.responses = Queue.Queue() + self.unanswered_requests = {} + + def poke(self): + # push a fake response so that the getting thread exits its loop + self.responses.put(None) + + def queue_json_response(self, c): + + #print "<--",c + msg_id = c.get('id') + error = c.get('error') + + if error: + print "received error:", c + return + + if msg_id is not None: + method, params = self.unanswered_requests.pop(msg_id) + result = c.get('result') + else: + # notification + method = c.get('method') + params = c.get('params') + + if method == 'blockchain.numblocks.subscribe': + result = params[0] + params = [] + + elif method == 'blockchain.address.subscribe': + addr = params[0] + result = params[1] + params = [addr] + + self.responses.put({'method':method, 'params':params, 'result':result}) + + + + def subscribe(self, addresses): + messages = [] + for addr in addresses: + messages.append(('blockchain.address.subscribe', [addr])) + self.send(messages) + + + + +class PollingInterface(Interface): + """ non-persistent connection. synchronous calls""" + + def __init__(self, host, port): + Interface.__init__(self, host, port) + self.session_id = None + + def get_history(self, address): + self.send([('blockchain.address.get_history', [address] )]) + + def poll(self): + pass + #if is_new or wallet.remote_url: + # self.was_updated = True + # is_new = wallet.synchronize() + # wallet.update_tx_history() + # wallet.save() + # return is_new + #else: + # return False + + def run(self): + self.is_connected = True + while self.is_connected: + try: + if self.session_id: + self.poll() + time.sleep(self.poll_interval) + except socket.gaierror: + break + except socket.error: + break + except: + traceback.print_exc(file=sys.stdout) + break + + self.is_connected = False + self.poke() + + + + + + + + + +class HttpStratumInterface(PollingInterface): + + def poll(self): + self.send([]) + + def send(self, messages): + import urllib2, json, time, cookielib + + cj = cookielib.CookieJar() + opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) + urllib2.install_opener(opener) + + t1 = time.time() + + data = [] + for m in messages: + method, params = m + if type(params) != type([]): params = [params] + data.append( { 'method':method, 'id':self.message_id, 'params':params } ) + self.unanswered_requests[self.message_id] = method, params + self.message_id += 1 + + if data: + data_json = json.dumps(data) + else: + # poll with GET + data_json = None + + host = 'http://%s:%d'%( self.host, self.port ) + headers = {'content-type': 'application/json'} + if self.session_id: + headers['cookie'] = 'SESSION=%s'%self.session_id + + req = urllib2.Request(host, data_json, headers) + response_stream = urllib2.urlopen(req) + + for index, cookie in enumerate(cj): + if cookie.name=='SESSION': + self.session_id = cookie.value + + response = response_stream.read() + if response: + response = json.loads( response ) + if type(response) is not type([]): + self.queue_json_response(response) + else: + for item in response: + self.queue_json_response(item) + + if response: + self.poll_interval = 1 + else: + if self.poll_interval < 15: + self.poll_interval += 1 + #print self.poll_interval, response + + self.rtime = time.time() - t1 + self.is_connected = True + + + + +class TcpStratumInterface(Interface): + """json-rpc over persistent TCP connection, asynchronous""" + + def __init__(self, host, port): + Interface.__init__(self, host, port) + self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) + self.s.settimeout(5*60) + self.s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + try: + self.s.connect(( self.host, self.port)) + self.is_connected = True + self.send([('server.version', [ELECTRUM_VERSION])]) + except: + self.is_connected = False + print "not connected" + + def run(self): + try: + out = '' + while self.is_connected: + try: msg = self.s.recv(1024) + except socket.timeout: + # ping the server with server.version, as a real ping does not exist yet + self.send([('server.version', [ELECTRUM_VERSION])]) + continue + out += msg + if msg == '': + self.is_connected = False + print "disconnected." + + while True: + s = out.find('\n') + if s==-1: break + c = out[0:s] + out = out[s+1:] + c = json.loads(c) + self.queue_json_response(c) + + except: + traceback.print_exc(file=sys.stdout) + + self.is_connected = False + print "poking" + self.poke() + + def send(self, messages): + out = '' + for m in messages: + method, params = m + request = json.dumps( { 'id':self.message_id, 'method':method, 'params':params } ) + self.unanswered_requests[self.message_id] = method, params + #print "-->",request + self.message_id += 1 + out += request + '\n' + self.s.send( out ) + + def get_history(self, addr): + self.send([('blockchain.address.get_history', [addr])]) + + + + + +class WalletSynchronizer(threading.Thread): + + def __init__(self, wallet, loop=False): + threading.Thread.__init__(self) + self.daemon = True + self.wallet = wallet + self.loop = loop + self.start_interface() + + + def handle_response(self, r): + if r is None: + return + + method = r['method'] + params = r['params'] + result = r['result'] + + if method == 'server.banner': + self.wallet.banner = result + self.wallet.was_updated = True + + elif method == 'server.peers.subscribe': + servers = [] + for item in result: + s = [] + host = item[1] + ports = [] + if len(item)>2: + for v in item[2]: + if re.match("[th]\d+",v): + ports.append((v[0],v[1:])) + if ports: + servers.append( (host, ports) ) + self.interface.servers = servers + + elif method == 'blockchain.address.subscribe': + addr = params[0] + self.wallet.receive_status_callback(addr, result) + + elif method == 'blockchain.address.get_history': + addr = params[0] + self.wallet.receive_history_callback(addr, result) + self.wallet.was_updated = True + + elif method == 'blockchain.transaction.broadcast': + self.wallet.tx_result = result + self.wallet.tx_event.set() + + elif method == 'blockchain.numblocks.subscribe': + self.wallet.blocks = result + + elif method == 'server.version': + pass + + else: + print "unknown message:", method, params, result + + + def start_interface(self): + try: + host, port, protocol = self.wallet.server.split(':') + port = int(port) + except: + self.wallet.pick_random_server() + host, port, protocol = self.wallet.server.split(':') + port = int(port) + + #print protocol, host, port + if protocol == 't': + InterfaceClass = TcpStratumInterface + elif protocol == 'h': + InterfaceClass = HttpStratumInterface + else: + print "unknown protocol" + InterfaceClass = TcpStratumInterface + + self.interface = InterfaceClass(host, port) + self.interface.start() + self.wallet.interface = self.interface + + if self.interface.is_connected: + self.wallet.start_session(self.interface) + + + + def run(self): + import socket, time + while True: + while self.interface.is_connected: + new_addresses = self.wallet.synchronize() + if new_addresses: + self.interface.subscribe(new_addresses) + + if self.wallet.is_up_to_date(): + if not self.wallet.up_to_date: + self.wallet.up_to_date = True + self.wallet.was_updated = True + self.wallet.up_to_date_event.set() + else: + if self.wallet.up_to_date: + self.wallet.up_to_date = False + self.wallet.was_updated = True + + if self.wallet.was_updated: + self.wallet.gui_callback() + self.wallet.was_updated = False + + response = self.interface.responses.get() + self.handle_response(response) + + print "disconnected, gui callback" + self.wallet.gui_callback() + if self.loop: + time.sleep(5) + self.start_interface() + continue + else: + break + + + diff --git a/lib/mnemonic.py b/lib/mnemonic.py new file mode 100644 index 0000000..82d2624 --- /dev/null +++ b/lib/mnemonic.py @@ -0,0 +1,1689 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + + +# list of words from http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry + +words = [ +"like", +"just", +"love", +"know", +"never", +"want", +"time", +"out", +"there", +"make", +"look", +"eye", +"down", +"only", +"think", +"heart", +"back", +"then", +"into", +"about", +"more", +"away", +"still", +"them", +"take", +"thing", +"even", +"through", +"long", +"always", +"world", +"too", +"friend", +"tell", +"try", +"hand", +"thought", +"over", +"here", +"other", +"need", +"smile", +"again", +"much", +"cry", +"been", +"night", +"ever", +"little", +"said", +"end", +"some", +"those", +"around", +"mind", +"people", +"girl", +"leave", +"dream", +"left", +"turn", +"myself", +"give", +"nothing", +"really", +"off", +"before", +"something", +"find", +"walk", +"wish", +"good", +"once", +"place", +"ask", +"stop", +"keep", +"watch", +"seem", +"everything", +"wait", +"got", +"yet", +"made", +"remember", +"start", +"alone", +"run", +"hope", +"maybe", +"believe", +"body", +"hate", +"after", +"close", +"talk", +"stand", +"own", +"each", +"hurt", +"help", +"home", +"god", +"soul", +"new", +"many", +"two", +"inside", +"should", +"true", +"first", +"fear", +"mean", +"better", +"play", +"another", +"gone", +"change", +"use", +"wonder", +"someone", +"hair", +"cold", +"open", +"best", +"any", +"behind", +"happen", +"water", +"dark", +"laugh", +"stay", +"forever", +"name", +"work", +"show", +"sky", +"break", +"came", +"deep", +"door", +"put", +"black", +"together", +"upon", +"happy", +"such", +"great", +"white", +"matter", +"fill", +"past", +"please", +"burn", +"cause", +"enough", +"touch", +"moment", +"soon", +"voice", +"scream", +"anything", +"stare", +"sound", +"red", +"everyone", +"hide", +"kiss", +"truth", +"death", +"beautiful", +"mine", +"blood", +"broken", +"very", +"pass", +"next", +"forget", +"tree", +"wrong", +"air", +"mother", +"understand", +"lip", +"hit", +"wall", +"memory", +"sleep", +"free", +"high", +"realize", +"school", +"might", +"skin", +"sweet", +"perfect", +"blue", +"kill", +"breath", +"dance", +"against", +"fly", +"between", +"grow", +"strong", +"under", +"listen", +"bring", +"sometimes", +"speak", +"pull", +"person", +"become", +"family", +"begin", +"ground", +"real", +"small", +"father", +"sure", +"feet", +"rest", +"young", +"finally", +"land", +"across", +"today", +"different", +"guy", +"line", +"fire", +"reason", +"reach", +"second", +"slowly", +"write", +"eat", +"smell", +"mouth", +"step", +"learn", +"three", +"floor", +"promise", +"breathe", +"darkness", +"push", +"earth", +"guess", +"save", +"song", +"above", +"along", +"both", +"color", +"house", +"almost", +"sorry", +"anymore", +"brother", +"okay", +"dear", +"game", +"fade", +"already", +"apart", +"warm", +"beauty", +"heard", +"notice", +"question", +"shine", +"began", +"piece", +"whole", +"shadow", +"secret", +"street", +"within", +"finger", +"point", +"morning", +"whisper", +"child", +"moon", +"green", +"story", +"glass", +"kid", +"silence", +"since", +"soft", +"yourself", +"empty", +"shall", +"angel", +"answer", +"baby", +"bright", +"dad", +"path", +"worry", +"hour", +"drop", +"follow", +"power", +"war", +"half", +"flow", +"heaven", +"act", +"chance", +"fact", +"least", +"tired", +"children", +"near", +"quite", +"afraid", +"rise", +"sea", +"taste", +"window", +"cover", +"nice", +"trust", +"lot", +"sad", +"cool", +"force", +"peace", +"return", +"blind", +"easy", +"ready", +"roll", +"rose", +"drive", +"held", +"music", +"beneath", +"hang", +"mom", +"paint", +"emotion", +"quiet", +"clear", +"cloud", +"few", +"pretty", +"bird", +"outside", +"paper", +"picture", +"front", +"rock", +"simple", +"anyone", +"meant", +"reality", +"road", +"sense", +"waste", +"bit", +"leaf", +"thank", +"happiness", +"meet", +"men", +"smoke", +"truly", +"decide", +"self", +"age", +"book", +"form", +"alive", +"carry", +"escape", +"damn", +"instead", +"able", +"ice", +"minute", +"throw", +"catch", +"leg", +"ring", +"course", +"goodbye", +"lead", +"poem", +"sick", +"corner", +"desire", +"known", +"problem", +"remind", +"shoulder", +"suppose", +"toward", +"wave", +"drink", +"jump", +"woman", +"pretend", +"sister", +"week", +"human", +"joy", +"crack", +"grey", +"pray", +"surprise", +"dry", +"knee", +"less", +"search", +"bleed", +"caught", +"clean", +"embrace", +"future", +"king", +"son", +"sorrow", +"chest", +"hug", +"remain", +"sat", +"worth", +"blow", +"daddy", +"final", +"parent", +"tight", +"also", +"create", +"lonely", +"safe", +"cross", +"dress", +"evil", +"silent", +"bone", +"fate", +"perhaps", +"anger", +"class", +"scar", +"snow", +"tiny", +"tonight", +"continue", +"control", +"dog", +"edge", +"mirror", +"month", +"suddenly", +"comfort", +"given", +"loud", +"quickly", +"gaze", +"plan", +"rush", +"stone", +"town", +"battle", +"ignore", +"spirit", +"stood", +"stupid", +"yours", +"brown", +"build", +"dust", +"hey", +"kept", +"pay", +"phone", +"twist", +"although", +"ball", +"beyond", +"hidden", +"nose", +"taken", +"fail", +"float", +"pure", +"somehow", +"wash", +"wrap", +"angry", +"cheek", +"creature", +"forgotten", +"heat", +"rip", +"single", +"space", +"special", +"weak", +"whatever", +"yell", +"anyway", +"blame", +"job", +"choose", +"country", +"curse", +"drift", +"echo", +"figure", +"grew", +"laughter", +"neck", +"suffer", +"worse", +"yeah", +"disappear", +"foot", +"forward", +"knife", +"mess", +"somewhere", +"stomach", +"storm", +"beg", +"idea", +"lift", +"offer", +"breeze", +"field", +"five", +"often", +"simply", +"stuck", +"win", +"allow", +"confuse", +"enjoy", +"except", +"flower", +"seek", +"strength", +"calm", +"grin", +"gun", +"heavy", +"hill", +"large", +"ocean", +"shoe", +"sigh", +"straight", +"summer", +"tongue", +"accept", +"crazy", +"everyday", +"exist", +"grass", +"mistake", +"sent", +"shut", +"surround", +"table", +"ache", +"brain", +"destroy", +"heal", +"nature", +"shout", +"sign", +"stain", +"choice", +"doubt", +"glance", +"glow", +"mountain", +"queen", +"stranger", +"throat", +"tomorrow", +"city", +"either", +"fish", +"flame", +"rather", +"shape", +"spin", +"spread", +"ash", +"distance", +"finish", +"image", +"imagine", +"important", +"nobody", +"shatter", +"warmth", +"became", +"feed", +"flesh", +"funny", +"lust", +"shirt", +"trouble", +"yellow", +"attention", +"bare", +"bite", +"money", +"protect", +"amaze", +"appear", +"born", +"choke", +"completely", +"daughter", +"fresh", +"friendship", +"gentle", +"probably", +"six", +"deserve", +"expect", +"grab", +"middle", +"nightmare", +"river", +"thousand", +"weight", +"worst", +"wound", +"barely", +"bottle", +"cream", +"regret", +"relationship", +"stick", +"test", +"crush", +"endless", +"fault", +"itself", +"rule", +"spill", +"art", +"circle", +"join", +"kick", +"mask", +"master", +"passion", +"quick", +"raise", +"smooth", +"unless", +"wander", +"actually", +"broke", +"chair", +"deal", +"favorite", +"gift", +"note", +"number", +"sweat", +"box", +"chill", +"clothes", +"lady", +"mark", +"park", +"poor", +"sadness", +"tie", +"animal", +"belong", +"brush", +"consume", +"dawn", +"forest", +"innocent", +"pen", +"pride", +"stream", +"thick", +"clay", +"complete", +"count", +"draw", +"faith", +"press", +"silver", +"struggle", +"surface", +"taught", +"teach", +"wet", +"bless", +"chase", +"climb", +"enter", +"letter", +"melt", +"metal", +"movie", +"stretch", +"swing", +"vision", +"wife", +"beside", +"crash", +"forgot", +"guide", +"haunt", +"joke", +"knock", +"plant", +"pour", +"prove", +"reveal", +"steal", +"stuff", +"trip", +"wood", +"wrist", +"bother", +"bottom", +"crawl", +"crowd", +"fix", +"forgive", +"frown", +"grace", +"loose", +"lucky", +"party", +"release", +"surely", +"survive", +"teacher", +"gently", +"grip", +"speed", +"suicide", +"travel", +"treat", +"vein", +"written", +"cage", +"chain", +"conversation", +"date", +"enemy", +"however", +"interest", +"million", +"page", +"pink", +"proud", +"sway", +"themselves", +"winter", +"church", +"cruel", +"cup", +"demon", +"experience", +"freedom", +"pair", +"pop", +"purpose", +"respect", +"shoot", +"softly", +"state", +"strange", +"bar", +"birth", +"curl", +"dirt", +"excuse", +"lord", +"lovely", +"monster", +"order", +"pack", +"pants", +"pool", +"scene", +"seven", +"shame", +"slide", +"ugly", +"among", +"blade", +"blonde", +"closet", +"creek", +"deny", +"drug", +"eternity", +"gain", +"grade", +"handle", +"key", +"linger", +"pale", +"prepare", +"swallow", +"swim", +"tremble", +"wheel", +"won", +"cast", +"cigarette", +"claim", +"college", +"direction", +"dirty", +"gather", +"ghost", +"hundred", +"loss", +"lung", +"orange", +"present", +"swear", +"swirl", +"twice", +"wild", +"bitter", +"blanket", +"doctor", +"everywhere", +"flash", +"grown", +"knowledge", +"numb", +"pressure", +"radio", +"repeat", +"ruin", +"spend", +"unknown", +"buy", +"clock", +"devil", +"early", +"false", +"fantasy", +"pound", +"precious", +"refuse", +"sheet", +"teeth", +"welcome", +"add", +"ahead", +"block", +"bury", +"caress", +"content", +"depth", +"despite", +"distant", +"marry", +"purple", +"threw", +"whenever", +"bomb", +"dull", +"easily", +"grasp", +"hospital", +"innocence", +"normal", +"receive", +"reply", +"rhyme", +"shade", +"someday", +"sword", +"toe", +"visit", +"asleep", +"bought", +"center", +"consider", +"flat", +"hero", +"history", +"ink", +"insane", +"muscle", +"mystery", +"pocket", +"reflection", +"shove", +"silently", +"smart", +"soldier", +"spot", +"stress", +"train", +"type", +"view", +"whether", +"bus", +"energy", +"explain", +"holy", +"hunger", +"inch", +"magic", +"mix", +"noise", +"nowhere", +"prayer", +"presence", +"shock", +"snap", +"spider", +"study", +"thunder", +"trail", +"admit", +"agree", +"bag", +"bang", +"bound", +"butterfly", +"cute", +"exactly", +"explode", +"familiar", +"fold", +"further", +"pierce", +"reflect", +"scent", +"selfish", +"sharp", +"sink", +"spring", +"stumble", +"universe", +"weep", +"women", +"wonderful", +"action", +"ancient", +"attempt", +"avoid", +"birthday", +"branch", +"chocolate", +"core", +"depress", +"drunk", +"especially", +"focus", +"fruit", +"honest", +"match", +"palm", +"perfectly", +"pillow", +"pity", +"poison", +"roar", +"shift", +"slightly", +"thump", +"truck", +"tune", +"twenty", +"unable", +"wipe", +"wrote", +"coat", +"constant", +"dinner", +"drove", +"egg", +"eternal", +"flight", +"flood", +"frame", +"freak", +"gasp", +"glad", +"hollow", +"motion", +"peer", +"plastic", +"root", +"screen", +"season", +"sting", +"strike", +"team", +"unlike", +"victim", +"volume", +"warn", +"weird", +"attack", +"await", +"awake", +"built", +"charm", +"crave", +"despair", +"fought", +"grant", +"grief", +"horse", +"limit", +"message", +"ripple", +"sanity", +"scatter", +"serve", +"split", +"string", +"trick", +"annoy", +"blur", +"boat", +"brave", +"clearly", +"cling", +"connect", +"fist", +"forth", +"imagination", +"iron", +"jock", +"judge", +"lesson", +"milk", +"misery", +"nail", +"naked", +"ourselves", +"poet", +"possible", +"princess", +"sail", +"size", +"snake", +"society", +"stroke", +"torture", +"toss", +"trace", +"wise", +"bloom", +"bullet", +"cell", +"check", +"cost", +"darling", +"during", +"footstep", +"fragile", +"hallway", +"hardly", +"horizon", +"invisible", +"journey", +"midnight", +"mud", +"nod", +"pause", +"relax", +"shiver", +"sudden", +"value", +"youth", +"abuse", +"admire", +"blink", +"breast", +"bruise", +"constantly", +"couple", +"creep", +"curve", +"difference", +"dumb", +"emptiness", +"gotta", +"honor", +"plain", +"planet", +"recall", +"rub", +"ship", +"slam", +"soar", +"somebody", +"tightly", +"weather", +"adore", +"approach", +"bond", +"bread", +"burst", +"candle", +"coffee", +"cousin", +"crime", +"desert", +"flutter", +"frozen", +"grand", +"heel", +"hello", +"language", +"level", +"movement", +"pleasure", +"powerful", +"random", +"rhythm", +"settle", +"silly", +"slap", +"sort", +"spoken", +"steel", +"threaten", +"tumble", +"upset", +"aside", +"awkward", +"bee", +"blank", +"board", +"button", +"card", +"carefully", +"complain", +"crap", +"deeply", +"discover", +"drag", +"dread", +"effort", +"entire", +"fairy", +"giant", +"gotten", +"greet", +"illusion", +"jeans", +"leap", +"liquid", +"march", +"mend", +"nervous", +"nine", +"replace", +"rope", +"spine", +"stole", +"terror", +"accident", +"apple", +"balance", +"boom", +"childhood", +"collect", +"demand", +"depression", +"eventually", +"faint", +"glare", +"goal", +"group", +"honey", +"kitchen", +"laid", +"limb", +"machine", +"mere", +"mold", +"murder", +"nerve", +"painful", +"poetry", +"prince", +"rabbit", +"shelter", +"shore", +"shower", +"soothe", +"stair", +"steady", +"sunlight", +"tangle", +"tease", +"treasure", +"uncle", +"begun", +"bliss", +"canvas", +"cheer", +"claw", +"clutch", +"commit", +"crimson", +"crystal", +"delight", +"doll", +"existence", +"express", +"fog", +"football", +"gay", +"goose", +"guard", +"hatred", +"illuminate", +"mass", +"math", +"mourn", +"rich", +"rough", +"skip", +"stir", +"student", +"style", +"support", +"thorn", +"tough", +"yard", +"yearn", +"yesterday", +"advice", +"appreciate", +"autumn", +"bank", +"beam", +"bowl", +"capture", +"carve", +"collapse", +"confusion", +"creation", +"dove", +"feather", +"girlfriend", +"glory", +"government", +"harsh", +"hop", +"inner", +"loser", +"moonlight", +"neighbor", +"neither", +"peach", +"pig", +"praise", +"screw", +"shield", +"shimmer", +"sneak", +"stab", +"subject", +"throughout", +"thrown", +"tower", +"twirl", +"wow", +"army", +"arrive", +"bathroom", +"bump", +"cease", +"cookie", +"couch", +"courage", +"dim", +"guilt", +"howl", +"hum", +"husband", +"insult", +"led", +"lunch", +"mock", +"mostly", +"natural", +"nearly", +"needle", +"nerd", +"peaceful", +"perfection", +"pile", +"price", +"remove", +"roam", +"sanctuary", +"serious", +"shiny", +"shook", +"sob", +"stolen", +"tap", +"vain", +"void", +"warrior", +"wrinkle", +"affection", +"apologize", +"blossom", +"bounce", +"bridge", +"cheap", +"crumble", +"decision", +"descend", +"desperately", +"dig", +"dot", +"flip", +"frighten", +"heartbeat", +"huge", +"lazy", +"lick", +"odd", +"opinion", +"process", +"puzzle", +"quietly", +"retreat", +"score", +"sentence", +"separate", +"situation", +"skill", +"soak", +"square", +"stray", +"taint", +"task", +"tide", +"underneath", +"veil", +"whistle", +"anywhere", +"bedroom", +"bid", +"bloody", +"burden", +"careful", +"compare", +"concern", +"curtain", +"decay", +"defeat", +"describe", +"double", +"dreamer", +"driver", +"dwell", +"evening", +"flare", +"flicker", +"grandma", +"guitar", +"harm", +"horrible", +"hungry", +"indeed", +"lace", +"melody", +"monkey", +"nation", +"object", +"obviously", +"rainbow", +"salt", +"scratch", +"shown", +"shy", +"stage", +"stun", +"third", +"tickle", +"useless", +"weakness", +"worship", +"worthless", +"afternoon", +"beard", +"boyfriend", +"bubble", +"busy", +"certain", +"chin", +"concrete", +"desk", +"diamond", +"doom", +"drawn", +"due", +"felicity", +"freeze", +"frost", +"garden", +"glide", +"harmony", +"hopefully", +"hunt", +"jealous", +"lightning", +"mama", +"mercy", +"peel", +"physical", +"position", +"pulse", +"punch", +"quit", +"rant", +"respond", +"salty", +"sane", +"satisfy", +"savior", +"sheep", +"slept", +"social", +"sport", +"tuck", +"utter", +"valley", +"wolf", +"aim", +"alas", +"alter", +"arrow", +"awaken", +"beaten", +"belief", +"brand", +"ceiling", +"cheese", +"clue", +"confidence", +"connection", +"daily", +"disguise", +"eager", +"erase", +"essence", +"everytime", +"expression", +"fan", +"flag", +"flirt", +"foul", +"fur", +"giggle", +"glorious", +"ignorance", +"law", +"lifeless", +"measure", +"mighty", +"muse", +"north", +"opposite", +"paradise", +"patience", +"patient", +"pencil", +"petal", +"plate", +"ponder", +"possibly", +"practice", +"slice", +"spell", +"stock", +"strife", +"strip", +"suffocate", +"suit", +"tender", +"tool", +"trade", +"velvet", +"verse", +"waist", +"witch", +"aunt", +"bench", +"bold", +"cap", +"certainly", +"click", +"companion", +"creator", +"dart", +"delicate", +"determine", +"dish", +"dragon", +"drama", +"drum", +"dude", +"everybody", +"feast", +"forehead", +"former", +"fright", +"fully", +"gas", +"hook", +"hurl", +"invite", +"juice", +"manage", +"moral", +"possess", +"raw", +"rebel", +"royal", +"scale", +"scary", +"several", +"slight", +"stubborn", +"swell", +"talent", +"tea", +"terrible", +"thread", +"torment", +"trickle", +"usually", +"vast", +"violence", +"weave", +"acid", +"agony", +"ashamed", +"awe", +"belly", +"blend", +"blush", +"character", +"cheat", +"common", +"company", +"coward", +"creak", +"danger", +"deadly", +"defense", +"define", +"depend", +"desperate", +"destination", +"dew", +"duck", +"dusty", +"embarrass", +"engine", +"example", +"explore", +"foe", +"freely", +"frustrate", +"generation", +"glove", +"guilty", +"health", +"hurry", +"idiot", +"impossible", +"inhale", +"jaw", +"kingdom", +"mention", +"mist", +"moan", +"mumble", +"mutter", +"observe", +"ode", +"pathetic", +"pattern", +"pie", +"prefer", +"puff", +"rape", +"rare", +"revenge", +"rude", +"scrape", +"spiral", +"squeeze", +"strain", +"sunset", +"suspend", +"sympathy", +"thigh", +"throne", +"total", +"unseen", +"weapon", +"weary" +] + + + +n = 1626 + +# Note about US patent no 5892470: Here each word does not represent a given digit. +# Instead, the digit represented by a word is variable, it depends on the previous word. + +def mn_encode( message ): + out = [] + for i in range(len(message)/8): + word = message[8*i:8*i+8] + x = int(word, 16) + w1 = (x%n) + w2 = ((x/n) + w1)%n + w3 = ((x/n/n) + w2)%n + out += [ words[w1], words[w2], words[w3] ] + return out + +def mn_decode( wlist ): + out = '' + for i in range(len(wlist)/3): + word1, word2, word3 = wlist[3*i:3*i+3] + w1 = words.index(word1) + w2 = (words.index(word2))%n + w3 = (words.index(word3))%n + x = w1 +n*((w2-w1)%n) +n*n*((w3-w2)%n) + out += '%08x'%x + return out + + +if __name__ == '__main__': + import sys + if len( sys.argv ) == 1: + print 'I need arguments: a hex string to encode, or a list of words to decode' + elif len( sys.argv ) == 2: + print ' '.join(mn_encode(sys.argv[1])) + else: + print mn_decode(sys.argv[1:]) diff --git a/lib/msqr.py b/lib/msqr.py new file mode 100644 index 0000000..da3c6fd --- /dev/null +++ b/lib/msqr.py @@ -0,0 +1,94 @@ +# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ + +def modular_sqrt(a, p): + """ Find a quadratic residue (mod p) of 'a'. p + must be an odd prime. + + Solve the congruence of the form: + x^2 = a (mod p) + And returns x. Note that p - x is also a root. + + 0 is returned is no square root exists for + these a and p. + + The Tonelli-Shanks algorithm is used (except + for some simple cases in which the solution + is known from an identity). This algorithm + runs in polynomial time (unless the + generalized Riemann hypothesis is false). + """ + # Simple cases + # + if legendre_symbol(a, p) != 1: + return 0 + elif a == 0: + return 0 + elif p == 2: + return p + elif p % 4 == 3: + return pow(a, (p + 1) / 4, p) + + # Partition p-1 to s * 2^e for an odd s (i.e. + # reduce all the powers of 2 from p-1) + # + s = p - 1 + e = 0 + while s % 2 == 0: + s /= 2 + e += 1 + + # Find some 'n' with a legendre symbol n|p = -1. + # Shouldn't take long. + # + n = 2 + while legendre_symbol(n, p) != -1: + n += 1 + + # Here be dragons! + # Read the paper "Square roots from 1; 24, 51, + # 10 to Dan Shanks" by Ezra Brown for more + # information + # + + # x is a guess of the square root that gets better + # with each iteration. + # b is the "fudge factor" - by how much we're off + # with the guess. The invariant x^2 = ab (mod p) + # is maintained throughout the loop. + # g is used for successive powers of n to update + # both a and b + # r is the exponent - decreases with each update + # + x = pow(a, (s + 1) / 2, p) + b = pow(a, s, p) + g = pow(n, s, p) + r = e + + while True: + t = b + m = 0 + for m in xrange(r): + if t == 1: + break + t = pow(t, 2, p) + + if m == 0: + return x + + gs = pow(g, 2 ** (r - m - 1), p) + g = (gs * gs) % p + x = (x * gs) % p + b = (b * g) % p + r = m + +def legendre_symbol(a, p): + """ Compute the Legendre symbol a|p using + Euler's criterion. p is a prime, a is + relatively prime to p (if p divides + a, then a|p = 0) + + Returns 1 if a has a square root modulo + p, -1 otherwise. + """ + ls = pow(a, (p - 1) / 2, p) + return -1 if ls == p - 1 else ls diff --git a/lib/pyqrnative.py b/lib/pyqrnative.py new file mode 100644 index 0000000..7f9c750 --- /dev/null +++ b/lib/pyqrnative.py @@ -0,0 +1,962 @@ +import math + +#from PIL import Image, ImageDraw + +#QRCode for Python +# +#Ported from the Javascript library by Sam Curren +# +#QRCode for Javascript +#http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/qrcode.js +# +#Copyright (c) 2009 Kazuhiko Arase +# +#URL: http://www.d-project.com/ +# +#Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# The word "QR Code" is registered trademark of +# DENSO WAVE INCORPORATED +# http://www.denso-wave.com/qrcode/faqpatent-e.html + + +class QR8bitByte: + + def __init__(self, data): + self.mode = QRMode.MODE_8BIT_BYTE + self.data = data + + def getLength(self): + return len(self.data) + + def write(self, buffer): + for i in range(len(self.data)): + #// not JIS ... + buffer.put(ord(self.data[i]), 8) + def __repr__(self): + return self.data + +class QRCode: + def __init__(self, typeNumber, errorCorrectLevel): + self.typeNumber = typeNumber + self.errorCorrectLevel = errorCorrectLevel + self.modules = None + self.moduleCount = 0 + self.dataCache = None + self.dataList = [] + def addData(self, data): + newData = QR8bitByte(data) + self.dataList.append(newData) + self.dataCache = None + def isDark(self, row, col): + if (row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col): + raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) + return self.modules[row][col] + def getModuleCount(self): + return self.moduleCount + def make(self): + self.makeImpl(False, self.getBestMaskPattern() ) + def makeImpl(self, test, maskPattern): + + self.moduleCount = self.typeNumber * 4 + 17 + self.modules = [None for x in range(self.moduleCount)] + + for row in range(self.moduleCount): + + self.modules[row] = [None for x in range(self.moduleCount)] + + for col in range(self.moduleCount): + self.modules[row][col] = None #//(col + row) % 3; + + self.setupPositionProbePattern(0, 0) + self.setupPositionProbePattern(self.moduleCount - 7, 0) + self.setupPositionProbePattern(0, self.moduleCount - 7) + self.setupPositionAdjustPattern() + self.setupTimingPattern() + self.setupTypeInfo(test, maskPattern) + + if (self.typeNumber >= 7): + self.setupTypeNumber(test) + + if (self.dataCache == None): + self.dataCache = QRCode.createData(self.typeNumber, self.errorCorrectLevel, self.dataList) + self.mapData(self.dataCache, maskPattern) + + def setupPositionProbePattern(self, row, col): + + for r in range(-1, 8): + + if (row + r <= -1 or self.moduleCount <= row + r): continue + + for c in range(-1, 8): + + if (col + c <= -1 or self.moduleCount <= col + c): continue + + if ( (0 <= r and r <= 6 and (c == 0 or c == 6) ) + or (0 <= c and c <= 6 and (r == 0 or r == 6) ) + or (2 <= r and r <= 4 and 2 <= c and c <= 4) ): + self.modules[row + r][col + c] = True; + else: + self.modules[row + r][col + c] = False; + + def getBestMaskPattern(self): + + minLostPoint = 0 + pattern = 0 + + for i in range(8): + + self.makeImpl(True, i); + + lostPoint = QRUtil.getLostPoint(self); + + if (i == 0 or minLostPoint > lostPoint): + minLostPoint = lostPoint + pattern = i + + return pattern + + + def setupTimingPattern(self): + + for r in range(8, self.moduleCount - 8): + if (self.modules[r][6] != None): + continue + self.modules[r][6] = (r % 2 == 0) + + for c in range(8, self.moduleCount - 8): + if (self.modules[6][c] != None): + continue + self.modules[6][c] = (c % 2 == 0) + + def setupPositionAdjustPattern(self): + + pos = QRUtil.getPatternPosition(self.typeNumber) + + for i in range(len(pos)): + + for j in range(len(pos)): + + row = pos[i] + col = pos[j] + + if (self.modules[row][col] != None): + continue + + for r in range(-2, 3): + + for c in range(-2, 3): + + if (r == -2 or r == 2 or c == -2 or c == 2 or (r == 0 and c == 0) ): + self.modules[row + r][col + c] = True + else: + self.modules[row + r][col + c] = False + + def setupTypeNumber(self, test): + + bits = QRUtil.getBCHTypeNumber(self.typeNumber) + + for i in range(18): + mod = (not test and ( (bits >> i) & 1) == 1) + self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = mod; + + for i in range(18): + mod = (not test and ( (bits >> i) & 1) == 1) + self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = mod; + + def setupTypeInfo(self, test, maskPattern): + + data = (self.errorCorrectLevel << 3) | maskPattern + bits = QRUtil.getBCHTypeInfo(data) + + #// vertical + for i in range(15): + + mod = (not test and ( (bits >> i) & 1) == 1) + + if (i < 6): + self.modules[i][8] = mod + elif (i < 8): + self.modules[i + 1][8] = mod + else: + self.modules[self.moduleCount - 15 + i][8] = mod + + #// horizontal + for i in range(15): + + mod = (not test and ( (bits >> i) & 1) == 1); + + if (i < 8): + self.modules[8][self.moduleCount - i - 1] = mod + elif (i < 9): + self.modules[8][15 - i - 1 + 1] = mod + else: + self.modules[8][15 - i - 1] = mod + + #// fixed module + self.modules[self.moduleCount - 8][8] = (not test) + + def mapData(self, data, maskPattern): + + inc = -1 + row = self.moduleCount - 1 + bitIndex = 7 + byteIndex = 0 + + for col in range(self.moduleCount - 1, 0, -2): + + if (col == 6): col-=1 + + while (True): + + for c in range(2): + + if (self.modules[row][col - c] == None): + + dark = False + + if (byteIndex < len(data)): + dark = ( ( (data[byteIndex] >> bitIndex) & 1) == 1) + + mask = QRUtil.getMask(maskPattern, row, col - c) + + if (mask): + dark = not dark + + self.modules[row][col - c] = dark + bitIndex-=1 + + if (bitIndex == -1): + byteIndex+=1 + bitIndex = 7 + + row += inc + + if (row < 0 or self.moduleCount <= row): + row -= inc + inc = -inc + break + PAD0 = 0xEC + PAD1 = 0x11 + + @staticmethod + def createData(typeNumber, errorCorrectLevel, dataList): + + rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel) + + buffer = QRBitBuffer(); + + for i in range(len(dataList)): + data = dataList[i] + buffer.put(data.mode, 4) + buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber) ) + data.write(buffer) + + #// calc num max data. + totalDataCount = 0; + for i in range(len(rsBlocks)): + totalDataCount += rsBlocks[i].dataCount + + if (buffer.getLengthInBits() > totalDataCount * 8): + raise Exception("code length overflow. (" + + buffer.getLengthInBits() + + ">" + + totalDataCount * 8 + + ")") + + #// end code + if (buffer.getLengthInBits() + 4 <= totalDataCount * 8): + buffer.put(0, 4) + + #// padding + while (buffer.getLengthInBits() % 8 != 0): + buffer.putBit(False) + + #// padding + while (True): + + if (buffer.getLengthInBits() >= totalDataCount * 8): + break + buffer.put(QRCode.PAD0, 8) + + if (buffer.getLengthInBits() >= totalDataCount * 8): + break + buffer.put(QRCode.PAD1, 8) + + return QRCode.createBytes(buffer, rsBlocks) + + @staticmethod + def createBytes(buffer, rsBlocks): + + offset = 0 + + maxDcCount = 0 + maxEcCount = 0 + + dcdata = [0 for x in range(len(rsBlocks))] + ecdata = [0 for x in range(len(rsBlocks))] + + for r in range(len(rsBlocks)): + + dcCount = rsBlocks[r].dataCount + ecCount = rsBlocks[r].totalCount - dcCount + + maxDcCount = max(maxDcCount, dcCount) + maxEcCount = max(maxEcCount, ecCount) + + dcdata[r] = [0 for x in range(dcCount)] + + for i in range(len(dcdata[r])): + dcdata[r][i] = 0xff & buffer.buffer[i + offset] + offset += dcCount + + rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) + rawPoly = QRPolynomial(dcdata[r], rsPoly.getLength() - 1) + + modPoly = rawPoly.mod(rsPoly) + ecdata[r] = [0 for x in range(rsPoly.getLength()-1)] + for i in range(len(ecdata[r])): + modIndex = i + modPoly.getLength() - len(ecdata[r]) + if (modIndex >= 0): + ecdata[r][i] = modPoly.get(modIndex) + else: + ecdata[r][i] = 0 + + totalCodeCount = 0 + for i in range(len(rsBlocks)): + totalCodeCount += rsBlocks[i].totalCount + + data = [None for x in range(totalCodeCount)] + index = 0 + + for i in range(maxDcCount): + for r in range(len(rsBlocks)): + if (i < len(dcdata[r])): + data[index] = dcdata[r][i] + index+=1 + + for i in range(maxEcCount): + for r in range(len(rsBlocks)): + if (i < len(ecdata[r])): + data[index] = ecdata[r][i] + index+=1 + + return data + + +class QRMode: + MODE_NUMBER = 1 << 0 + MODE_ALPHA_NUM = 1 << 1 + MODE_8BIT_BYTE = 1 << 2 + MODE_KANJI = 1 << 3 + +class QRErrorCorrectLevel: + L = 1 + M = 0 + Q = 3 + H = 2 + +class QRMaskPattern: + PATTERN000 = 0 + PATTERN001 = 1 + PATTERN010 = 2 + PATTERN011 = 3 + PATTERN100 = 4 + PATTERN101 = 5 + PATTERN110 = 6 + PATTERN111 = 7 + +class QRUtil(object): + PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170] + ] + + G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0) + G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0) + G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) + + @staticmethod + def getBCHTypeInfo(data): + d = data << 10; + while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0): + d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) ) ) + + return ( (data << 10) | d) ^ QRUtil.G15_MASK + @staticmethod + def getBCHTypeNumber(data): + d = data << 12; + while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0): + d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) ) ) + return (data << 12) | d + @staticmethod + def getBCHDigit(data): + digit = 0; + while (data != 0): + digit += 1 + data >>= 1 + return digit + @staticmethod + def getPatternPosition(typeNumber): + return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1] + @staticmethod + def getMask(maskPattern, i, j): + if maskPattern == QRMaskPattern.PATTERN000 : return (i + j) % 2 == 0 + if maskPattern == QRMaskPattern.PATTERN001 : return i % 2 == 0 + if maskPattern == QRMaskPattern.PATTERN010 : return j % 3 == 0 + if maskPattern == QRMaskPattern.PATTERN011 : return (i + j) % 3 == 0 + if maskPattern == QRMaskPattern.PATTERN100 : return (math.floor(i / 2) + math.floor(j / 3) ) % 2 == 0 + if maskPattern == QRMaskPattern.PATTERN101 : return (i * j) % 2 + (i * j) % 3 == 0 + if maskPattern == QRMaskPattern.PATTERN110 : return ( (i * j) % 2 + (i * j) % 3) % 2 == 0 + if maskPattern == QRMaskPattern.PATTERN111 : return ( (i * j) % 3 + (i + j) % 2) % 2 == 0 + raise Exception("bad maskPattern:" + maskPattern); + @staticmethod + def getErrorCorrectPolynomial(errorCorrectLength): + a = QRPolynomial([1], 0); + for i in range(errorCorrectLength): + a = a.multiply(QRPolynomial([1, QRMath.gexp(i)], 0) ) + return a + @staticmethod + def getLengthInBits(mode, type): + + if 1 <= type and type < 10: + + #// 1 - 9 + + if mode == QRMode.MODE_NUMBER : return 10 + if mode == QRMode.MODE_ALPHA_NUM : return 9 + if mode == QRMode.MODE_8BIT_BYTE : return 8 + if mode == QRMode.MODE_KANJI : return 8 + raise Exception("mode:" + mode) + + elif (type < 27): + + #// 10 - 26 + + if mode == QRMode.MODE_NUMBER : return 12 + if mode == QRMode.MODE_ALPHA_NUM : return 11 + if mode == QRMode.MODE_8BIT_BYTE : return 16 + if mode == QRMode.MODE_KANJI : return 10 + raise Exception("mode:" + mode) + + elif (type < 41): + + #// 27 - 40 + + if mode == QRMode.MODE_NUMBER : return 14 + if mode == QRMode.MODE_ALPHA_NUM : return 13 + if mode == QRMode.MODE_8BIT_BYTE : return 16 + if mode == QRMode.MODE_KANJI : return 12 + raise Exception("mode:" + mode) + + else: + raise Exception("type:" + type) + @staticmethod + def getLostPoint(qrCode): + + moduleCount = qrCode.getModuleCount(); + + lostPoint = 0; + + #// LEVEL1 + + for row in range(moduleCount): + + for col in range(moduleCount): + + sameCount = 0; + dark = qrCode.isDark(row, col); + + for r in range(-1, 2): + + if (row + r < 0 or moduleCount <= row + r): + continue + + for c in range(-1, 2): + + if (col + c < 0 or moduleCount <= col + c): + continue + if (r == 0 and c == 0): + continue + + if (dark == qrCode.isDark(row + r, col + c) ): + sameCount+=1 + if (sameCount > 5): + lostPoint += (3 + sameCount - 5) + + #// LEVEL2 + + for row in range(moduleCount - 1): + for col in range(moduleCount - 1): + count = 0; + if (qrCode.isDark(row, col ) ): count+=1 + if (qrCode.isDark(row + 1, col ) ): count+=1 + if (qrCode.isDark(row, col + 1) ): count+=1 + if (qrCode.isDark(row + 1, col + 1) ): count+=1 + if (count == 0 or count == 4): + lostPoint += 3 + + #// LEVEL3 + + for row in range(moduleCount): + for col in range(moduleCount - 6): + if (qrCode.isDark(row, col) + and not qrCode.isDark(row, col + 1) + and qrCode.isDark(row, col + 2) + and qrCode.isDark(row, col + 3) + and qrCode.isDark(row, col + 4) + and not qrCode.isDark(row, col + 5) + and qrCode.isDark(row, col + 6) ): + lostPoint += 40 + + for col in range(moduleCount): + for row in range(moduleCount - 6): + if (qrCode.isDark(row, col) + and not qrCode.isDark(row + 1, col) + and qrCode.isDark(row + 2, col) + and qrCode.isDark(row + 3, col) + and qrCode.isDark(row + 4, col) + and not qrCode.isDark(row + 5, col) + and qrCode.isDark(row + 6, col) ): + lostPoint += 40 + + #// LEVEL4 + + darkCount = 0; + + for col in range(moduleCount): + for row in range(moduleCount): + if (qrCode.isDark(row, col) ): + darkCount+=1 + + ratio = abs(100 * darkCount / moduleCount / moduleCount - 50) / 5 + lostPoint += ratio * 10 + + return lostPoint + +class QRMath: + + @staticmethod + def glog(n): + if (n < 1): + raise Exception("glog(" + n + ")") + return LOG_TABLE[n]; + @staticmethod + def gexp(n): + while n < 0: + n += 255 + while n >= 256: + n -= 255 + return EXP_TABLE[n]; + +EXP_TABLE = [x for x in range(256)] + +LOG_TABLE = [x for x in range(256)] + +for i in range(8): + EXP_TABLE[i] = 1 << i; + +for i in range(8, 256): + EXP_TABLE[i] = EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8] + +for i in range(255): + LOG_TABLE[EXP_TABLE[i] ] = i + +class QRPolynomial: + + def __init__(self, num, shift): + + if (len(num) == 0): + raise Exception(num.length + "/" + shift) + + offset = 0 + + while offset < len(num) and num[offset] == 0: + offset += 1 + + self.num = [0 for x in range(len(num)-offset+shift)] + for i in range(len(num) - offset): + self.num[i] = num[i + offset] + + + def get(self, index): + return self.num[index] + def getLength(self): + return len(self.num) + def multiply(self, e): + num = [0 for x in range(self.getLength() + e.getLength() - 1)]; + + for i in range(self.getLength()): + for j in range(e.getLength()): + num[i + j] ^= QRMath.gexp(QRMath.glog(self.get(i) ) + QRMath.glog(e.get(j) ) ) + + return QRPolynomial(num, 0); + def mod(self, e): + + if (self.getLength() - e.getLength() < 0): + return self; + + ratio = QRMath.glog(self.get(0) ) - QRMath.glog(e.get(0) ) + + num = [0 for x in range(self.getLength())] + + for i in range(self.getLength()): + num[i] = self.get(i); + + for i in range(e.getLength()): + num[i] ^= QRMath.gexp(QRMath.glog(e.get(i) ) + ratio) + + # recursive call + return QRPolynomial(num, 0).mod(e); + +class QRRSBlock: + + RS_BLOCK_TABLE = [ + + #// L + #// M + #// Q + #// H + + #// 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + #// 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + #// 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + #// 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + #// 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + #// 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + #// 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + #// 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + #// 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + #// 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + + # 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + + # 12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + + # 13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + + # 14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + + # 15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12], + + # 16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + + # 17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + + # 18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + + # 19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + + # 20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + + # 21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + + # 22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + + # 23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + + # 24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + + # 25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + + # 26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + + # 27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + + # 28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + + # 29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + + # 30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + + # 31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + + # 32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + + # 33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + + # 34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + + # 35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + + # 36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + + # 37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + + # 38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + + # 39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + + # 40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16] + + ] + + def __init__(self, totalCount, dataCount): + self.totalCount = totalCount + self.dataCount = dataCount + + @staticmethod + def getRSBlocks(typeNumber, errorCorrectLevel): + rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); + if rsBlock == None: + raise Exception("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel) + + length = len(rsBlock) / 3 + + list = [] + + for i in range(length): + + count = rsBlock[i * 3 + 0] + totalCount = rsBlock[i * 3 + 1] + dataCount = rsBlock[i * 3 + 2] + + for j in range(count): + list.append(QRRSBlock(totalCount, dataCount)) + + return list; + + @staticmethod + def getRsBlockTable(typeNumber, errorCorrectLevel): + if errorCorrectLevel == QRErrorCorrectLevel.L: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + elif errorCorrectLevel == QRErrorCorrectLevel.M: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + elif errorCorrectLevel == QRErrorCorrectLevel.Q: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + elif errorCorrectLevel == QRErrorCorrectLevel.H: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + else: + return None; + +class QRBitBuffer: + def __init__(self): + self.buffer = [] + self.length = 0 + def __repr__(self): + return ".".join([str(n) for n in self.buffer]) + def get(self, index): + bufIndex = math.floor(index / 8) + val = ( (self.buffer[bufIndex] >> (7 - index % 8) ) & 1) == 1 + print "get ", val + return ( (self.buffer[bufIndex] >> (7 - index % 8) ) & 1) == 1 + def put(self, num, length): + for i in range(length): + self.putBit( ( (num >> (length - i - 1) ) & 1) == 1) + def getLengthInBits(self): + return self.length + def putBit(self, bit): + bufIndex = self.length // 8 + if len(self.buffer) <= bufIndex: + self.buffer.append(0) + if bit: + self.buffer[bufIndex] |= (0x80 >> (self.length % 8) ) + self.length+=1 diff --git a/lib/ripemd.py b/lib/ripemd.py new file mode 100644 index 0000000..a9d652c --- /dev/null +++ b/lib/ripemd.py @@ -0,0 +1,399 @@ +## ripemd.py - pure Python implementation of the RIPEMD-160 algorithm. +## Bjorn Edstrom 16 december 2007. +## +## Copyrights +## ========== +## +## This code is a derived from an implementation by Markus Friedl which is +## subject to the following license. This Python implementation is not +## subject to any other license. +## +##/* +## * Copyright (c) 2001 Markus Friedl. All rights reserved. +## * +## * Redistribution and use in source and binary forms, with or without +## * modification, are permitted provided that the following conditions +## * are met: +## * 1. Redistributions of source code must retain the above copyright +## * notice, this list of conditions and the following disclaimer. +## * 2. Redistributions in binary form must reproduce the above copyright +## * notice, this list of conditions and the following disclaimer in the +## * documentation and/or other materials provided with the distribution. +## * +## * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +## * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +## * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +## * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +## * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +## * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +## * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +## */ +##/* +## * Preneel, Bosselaers, Dobbertin, "The Cryptographic Hash Function RIPEMD-160", +## * RSA Laboratories, CryptoBytes, Volume 3, Number 2, Autumn 1997, +## * ftp://ftp.rsasecurity.com/pub/cryptobytes/crypto3n2.pdf +## */ + +try: + import psyco + psyco.full() +except ImportError: + pass + +#block_size = 1 +digest_size = 20 +digestsize = 20 + +class RIPEMD160: + """Return a new RIPEMD160 object. An optional string argument + may be provided; if present, this string will be automatically + hashed.""" + + def __init__(self, arg=None): + self.ctx = RMDContext() + if arg: + self.update(arg) + self.dig = None + + def update(self, arg): + """update(arg)""" + RMD160Update(self.ctx, arg, len(arg)) + self.dig = None + + def digest(self): + """digest()""" + if self.dig: + return self.dig + ctx = self.ctx.copy() + self.dig = RMD160Final(self.ctx) + self.ctx = ctx + return self.dig + + def hexdigest(self): + """hexdigest()""" + dig = self.digest() + hex_digest = '' + for d in dig: + hex_digest += '%02x' % ord(d) + return hex_digest + + def copy(self): + """copy()""" + import copy + return copy.deepcopy(self) + + + +def new(arg=None): + """Return a new RIPEMD160 object. An optional string argument + may be provided; if present, this string will be automatically + hashed.""" + return RIPEMD160(arg) + + + +# +# Private. +# + +class RMDContext: + def __init__(self): + self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE, + 0x10325476, 0xC3D2E1F0] # uint32 + self.count = 0 # uint64 + self.buffer = [0]*64 # uchar + def copy(self): + ctx = RMDContext() + ctx.state = self.state[:] + ctx.count = self.count + ctx.buffer = self.buffer[:] + return ctx + +K0 = 0x00000000 +K1 = 0x5A827999 +K2 = 0x6ED9EBA1 +K3 = 0x8F1BBCDC +K4 = 0xA953FD4E + +KK0 = 0x50A28BE6 +KK1 = 0x5C4DD124 +KK2 = 0x6D703EF3 +KK3 = 0x7A6D76E9 +KK4 = 0x00000000 + +def ROL(n, x): + return ((x << n) & 0xffffffff) | (x >> (32 - n)) + +def F0(x, y, z): + return x ^ y ^ z + +def F1(x, y, z): + return (x & y) | (((~x) % 0x100000000) & z) + +def F2(x, y, z): + return (x | ((~y) % 0x100000000)) ^ z + +def F3(x, y, z): + return (x & z) | (((~z) % 0x100000000) & y) + +def F4(x, y, z): + return x ^ (y | ((~z) % 0x100000000)) + +def R(a, b, c, d, e, Fj, Kj, sj, rj, X): + a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e + c = ROL(10, c) + return a % 0x100000000, c + +PADDING = [0x80] + [0]*63 + +import sys +import struct + +def RMD160Transform(state, block): #uint32 state[5], uchar block[64] + x = [0]*16 + if sys.byteorder == 'little': + x = struct.unpack('<16L', ''.join([chr(x) for x in block[0:64]])) + else: + raise "Error!!" + a = state[0] + b = state[1] + c = state[2] + d = state[3] + e = state[4] + + #/* Round 1 */ + a, c = R(a, b, c, d, e, F0, K0, 11, 0, x); + e, b = R(e, a, b, c, d, F0, K0, 14, 1, x); + d, a = R(d, e, a, b, c, F0, K0, 15, 2, x); + c, e = R(c, d, e, a, b, F0, K0, 12, 3, x); + b, d = R(b, c, d, e, a, F0, K0, 5, 4, x); + a, c = R(a, b, c, d, e, F0, K0, 8, 5, x); + e, b = R(e, a, b, c, d, F0, K0, 7, 6, x); + d, a = R(d, e, a, b, c, F0, K0, 9, 7, x); + c, e = R(c, d, e, a, b, F0, K0, 11, 8, x); + b, d = R(b, c, d, e, a, F0, K0, 13, 9, x); + a, c = R(a, b, c, d, e, F0, K0, 14, 10, x); + e, b = R(e, a, b, c, d, F0, K0, 15, 11, x); + d, a = R(d, e, a, b, c, F0, K0, 6, 12, x); + c, e = R(c, d, e, a, b, F0, K0, 7, 13, x); + b, d = R(b, c, d, e, a, F0, K0, 9, 14, x); + a, c = R(a, b, c, d, e, F0, K0, 8, 15, x); #/* #15 */ + #/* Round 2 */ + e, b = R(e, a, b, c, d, F1, K1, 7, 7, x); + d, a = R(d, e, a, b, c, F1, K1, 6, 4, x); + c, e = R(c, d, e, a, b, F1, K1, 8, 13, x); + b, d = R(b, c, d, e, a, F1, K1, 13, 1, x); + a, c = R(a, b, c, d, e, F1, K1, 11, 10, x); + e, b = R(e, a, b, c, d, F1, K1, 9, 6, x); + d, a = R(d, e, a, b, c, F1, K1, 7, 15, x); + c, e = R(c, d, e, a, b, F1, K1, 15, 3, x); + b, d = R(b, c, d, e, a, F1, K1, 7, 12, x); + a, c = R(a, b, c, d, e, F1, K1, 12, 0, x); + e, b = R(e, a, b, c, d, F1, K1, 15, 9, x); + d, a = R(d, e, a, b, c, F1, K1, 9, 5, x); + c, e = R(c, d, e, a, b, F1, K1, 11, 2, x); + b, d = R(b, c, d, e, a, F1, K1, 7, 14, x); + a, c = R(a, b, c, d, e, F1, K1, 13, 11, x); + e, b = R(e, a, b, c, d, F1, K1, 12, 8, x); #/* #31 */ + #/* Round 3 */ + d, a = R(d, e, a, b, c, F2, K2, 11, 3, x); + c, e = R(c, d, e, a, b, F2, K2, 13, 10, x); + b, d = R(b, c, d, e, a, F2, K2, 6, 14, x); + a, c = R(a, b, c, d, e, F2, K2, 7, 4, x); + e, b = R(e, a, b, c, d, F2, K2, 14, 9, x); + d, a = R(d, e, a, b, c, F2, K2, 9, 15, x); + c, e = R(c, d, e, a, b, F2, K2, 13, 8, x); + b, d = R(b, c, d, e, a, F2, K2, 15, 1, x); + a, c = R(a, b, c, d, e, F2, K2, 14, 2, x); + e, b = R(e, a, b, c, d, F2, K2, 8, 7, x); + d, a = R(d, e, a, b, c, F2, K2, 13, 0, x); + c, e = R(c, d, e, a, b, F2, K2, 6, 6, x); + b, d = R(b, c, d, e, a, F2, K2, 5, 13, x); + a, c = R(a, b, c, d, e, F2, K2, 12, 11, x); + e, b = R(e, a, b, c, d, F2, K2, 7, 5, x); + d, a = R(d, e, a, b, c, F2, K2, 5, 12, x); #/* #47 */ + #/* Round 4 */ + c, e = R(c, d, e, a, b, F3, K3, 11, 1, x); + b, d = R(b, c, d, e, a, F3, K3, 12, 9, x); + a, c = R(a, b, c, d, e, F3, K3, 14, 11, x); + e, b = R(e, a, b, c, d, F3, K3, 15, 10, x); + d, a = R(d, e, a, b, c, F3, K3, 14, 0, x); + c, e = R(c, d, e, a, b, F3, K3, 15, 8, x); + b, d = R(b, c, d, e, a, F3, K3, 9, 12, x); + a, c = R(a, b, c, d, e, F3, K3, 8, 4, x); + e, b = R(e, a, b, c, d, F3, K3, 9, 13, x); + d, a = R(d, e, a, b, c, F3, K3, 14, 3, x); + c, e = R(c, d, e, a, b, F3, K3, 5, 7, x); + b, d = R(b, c, d, e, a, F3, K3, 6, 15, x); + a, c = R(a, b, c, d, e, F3, K3, 8, 14, x); + e, b = R(e, a, b, c, d, F3, K3, 6, 5, x); + d, a = R(d, e, a, b, c, F3, K3, 5, 6, x); + c, e = R(c, d, e, a, b, F3, K3, 12, 2, x); #/* #63 */ + #/* Round 5 */ + b, d = R(b, c, d, e, a, F4, K4, 9, 4, x); + a, c = R(a, b, c, d, e, F4, K4, 15, 0, x); + e, b = R(e, a, b, c, d, F4, K4, 5, 5, x); + d, a = R(d, e, a, b, c, F4, K4, 11, 9, x); + c, e = R(c, d, e, a, b, F4, K4, 6, 7, x); + b, d = R(b, c, d, e, a, F4, K4, 8, 12, x); + a, c = R(a, b, c, d, e, F4, K4, 13, 2, x); + e, b = R(e, a, b, c, d, F4, K4, 12, 10, x); + d, a = R(d, e, a, b, c, F4, K4, 5, 14, x); + c, e = R(c, d, e, a, b, F4, K4, 12, 1, x); + b, d = R(b, c, d, e, a, F4, K4, 13, 3, x); + a, c = R(a, b, c, d, e, F4, K4, 14, 8, x); + e, b = R(e, a, b, c, d, F4, K4, 11, 11, x); + d, a = R(d, e, a, b, c, F4, K4, 8, 6, x); + c, e = R(c, d, e, a, b, F4, K4, 5, 15, x); + b, d = R(b, c, d, e, a, F4, K4, 6, 13, x); #/* #79 */ + + aa = a; + bb = b; + cc = c; + dd = d; + ee = e; + + a = state[0] + b = state[1] + c = state[2] + d = state[3] + e = state[4] + + #/* Parallel round 1 */ + a, c = R(a, b, c, d, e, F4, KK0, 8, 5, x) + e, b = R(e, a, b, c, d, F4, KK0, 9, 14, x) + d, a = R(d, e, a, b, c, F4, KK0, 9, 7, x) + c, e = R(c, d, e, a, b, F4, KK0, 11, 0, x) + b, d = R(b, c, d, e, a, F4, KK0, 13, 9, x) + a, c = R(a, b, c, d, e, F4, KK0, 15, 2, x) + e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x) + d, a = R(d, e, a, b, c, F4, KK0, 5, 4, x) + c, e = R(c, d, e, a, b, F4, KK0, 7, 13, x) + b, d = R(b, c, d, e, a, F4, KK0, 7, 6, x) + a, c = R(a, b, c, d, e, F4, KK0, 8, 15, x) + e, b = R(e, a, b, c, d, F4, KK0, 11, 8, x) + d, a = R(d, e, a, b, c, F4, KK0, 14, 1, x) + c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x) + b, d = R(b, c, d, e, a, F4, KK0, 12, 3, x) + a, c = R(a, b, c, d, e, F4, KK0, 6, 12, x) #/* #15 */ + #/* Parallel round 2 */ + e, b = R(e, a, b, c, d, F3, KK1, 9, 6, x) + d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x) + c, e = R(c, d, e, a, b, F3, KK1, 15, 3, x) + b, d = R(b, c, d, e, a, F3, KK1, 7, 7, x) + a, c = R(a, b, c, d, e, F3, KK1, 12, 0, x) + e, b = R(e, a, b, c, d, F3, KK1, 8, 13, x) + d, a = R(d, e, a, b, c, F3, KK1, 9, 5, x) + c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x) + b, d = R(b, c, d, e, a, F3, KK1, 7, 14, x) + a, c = R(a, b, c, d, e, F3, KK1, 7, 15, x) + e, b = R(e, a, b, c, d, F3, KK1, 12, 8, x) + d, a = R(d, e, a, b, c, F3, KK1, 7, 12, x) + c, e = R(c, d, e, a, b, F3, KK1, 6, 4, x) + b, d = R(b, c, d, e, a, F3, KK1, 15, 9, x) + a, c = R(a, b, c, d, e, F3, KK1, 13, 1, x) + e, b = R(e, a, b, c, d, F3, KK1, 11, 2, x) #/* #31 */ + #/* Parallel round 3 */ + d, a = R(d, e, a, b, c, F2, KK2, 9, 15, x) + c, e = R(c, d, e, a, b, F2, KK2, 7, 5, x) + b, d = R(b, c, d, e, a, F2, KK2, 15, 1, x) + a, c = R(a, b, c, d, e, F2, KK2, 11, 3, x) + e, b = R(e, a, b, c, d, F2, KK2, 8, 7, x) + d, a = R(d, e, a, b, c, F2, KK2, 6, 14, x) + c, e = R(c, d, e, a, b, F2, KK2, 6, 6, x) + b, d = R(b, c, d, e, a, F2, KK2, 14, 9, x) + a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x) + e, b = R(e, a, b, c, d, F2, KK2, 13, 8, x) + d, a = R(d, e, a, b, c, F2, KK2, 5, 12, x) + c, e = R(c, d, e, a, b, F2, KK2, 14, 2, x) + b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x) + a, c = R(a, b, c, d, e, F2, KK2, 13, 0, x) + e, b = R(e, a, b, c, d, F2, KK2, 7, 4, x) + d, a = R(d, e, a, b, c, F2, KK2, 5, 13, x) #/* #47 */ + #/* Parallel round 4 */ + c, e = R(c, d, e, a, b, F1, KK3, 15, 8, x) + b, d = R(b, c, d, e, a, F1, KK3, 5, 6, x) + a, c = R(a, b, c, d, e, F1, KK3, 8, 4, x) + e, b = R(e, a, b, c, d, F1, KK3, 11, 1, x) + d, a = R(d, e, a, b, c, F1, KK3, 14, 3, x) + c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x) + b, d = R(b, c, d, e, a, F1, KK3, 6, 15, x) + a, c = R(a, b, c, d, e, F1, KK3, 14, 0, x) + e, b = R(e, a, b, c, d, F1, KK3, 6, 5, x) + d, a = R(d, e, a, b, c, F1, KK3, 9, 12, x) + c, e = R(c, d, e, a, b, F1, KK3, 12, 2, x) + b, d = R(b, c, d, e, a, F1, KK3, 9, 13, x) + a, c = R(a, b, c, d, e, F1, KK3, 12, 9, x) + e, b = R(e, a, b, c, d, F1, KK3, 5, 7, x) + d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x) + c, e = R(c, d, e, a, b, F1, KK3, 8, 14, x) #/* #63 */ + #/* Parallel round 5 */ + b, d = R(b, c, d, e, a, F0, KK4, 8, 12, x) + a, c = R(a, b, c, d, e, F0, KK4, 5, 15, x) + e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x) + d, a = R(d, e, a, b, c, F0, KK4, 9, 4, x) + c, e = R(c, d, e, a, b, F0, KK4, 12, 1, x) + b, d = R(b, c, d, e, a, F0, KK4, 5, 5, x) + a, c = R(a, b, c, d, e, F0, KK4, 14, 8, x) + e, b = R(e, a, b, c, d, F0, KK4, 6, 7, x) + d, a = R(d, e, a, b, c, F0, KK4, 8, 6, x) + c, e = R(c, d, e, a, b, F0, KK4, 13, 2, x) + b, d = R(b, c, d, e, a, F0, KK4, 6, 13, x) + a, c = R(a, b, c, d, e, F0, KK4, 5, 14, x) + e, b = R(e, a, b, c, d, F0, KK4, 15, 0, x) + d, a = R(d, e, a, b, c, F0, KK4, 13, 3, x) + c, e = R(c, d, e, a, b, F0, KK4, 11, 9, x) + b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) #/* #79 */ + + t = (state[1] + cc + d) % 0x100000000; + state[1] = (state[2] + dd + e) % 0x100000000; + state[2] = (state[3] + ee + a) % 0x100000000; + state[3] = (state[4] + aa + b) % 0x100000000; + state[4] = (state[0] + bb + c) % 0x100000000; + state[0] = t % 0x100000000; + + pass + + +def RMD160Update(ctx, inp, inplen): + if type(inp) == str: + inp = [ord(i)&0xff for i in inp] + + have = (ctx.count / 8) % 64 + need = 64 - have + ctx.count += 8 * inplen + off = 0 + if inplen >= need: + if have: + for i in xrange(need): + ctx.buffer[have+i] = inp[i] + RMD160Transform(ctx.state, ctx.buffer) + off = need + have = 0 + while off + 64 <= inplen: + RMD160Transform(ctx.state, inp[off:]) #<--- + off += 64 + if off < inplen: + # memcpy(ctx->buffer + have, input+off, len-off); + for i in xrange(inplen - off): + ctx.buffer[have+i] = inp[off+i] + +def RMD160Final(ctx): + size = struct.pack(". + + +import sys, base64, os, re, hashlib, copy, operator, ast, threading, random + +try: + import ecdsa + from ecdsa.util import string_to_number, number_to_string +except: + print "python-ecdsa does not seem to be installed. Try 'sudo easy_install ecdsa'" + sys.exit(1) + +try: + import aes +except: + print "AES does not seem to be installed. Try 'sudo easy_install slowaes'" + sys.exit(1) + + +############ functions from pywallet ##################### + +addrtype = 0 + +def hash_160(public_key): + try: + md = hashlib.new('ripemd160') + md.update(hashlib.sha256(public_key).digest()) + return md.digest() + except: + import ripemd + md = ripemd.new(hashlib.sha256(public_key).digest()) + return md.digest() + + +def public_key_to_bc_address(public_key): + h160 = hash_160(public_key) + return hash_160_to_bc_address(h160) + +def hash_160_to_bc_address(h160): + vh160 = chr(addrtype) + h160 + h = Hash(vh160) + addr = vh160 + h[0:4] + return b58encode(addr) + +def bc_address_to_hash_160(addr): + bytes = b58decode(addr, 25) + return bytes[1:21] + +__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58base = len(__b58chars) + +def b58encode(v): + """ encode v, which is a string of bytes, to base58. + """ + + long_value = 0L + for (i, c) in enumerate(v[::-1]): + long_value += (256**i) * ord(c) + + result = '' + while long_value >= __b58base: + div, mod = divmod(long_value, __b58base) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + + # Bitcoin does a little leading-zero-compression: + # leading 0-bytes in the input become leading-1s + nPad = 0 + for c in v: + if c == '\0': nPad += 1 + else: break + + return (__b58chars[0]*nPad) + result + +def b58decode(v, length): + """ decode v into a string of len bytes + """ + long_value = 0L + for (i, c) in enumerate(v[::-1]): + long_value += __b58chars.find(c) * (__b58base**i) + + result = '' + while long_value >= 256: + div, mod = divmod(long_value, 256) + result = chr(mod) + result + long_value = div + result = chr(long_value) + result + + nPad = 0 + for c in v: + if c == __b58chars[0]: nPad += 1 + else: break + + result = chr(0)*nPad + result + if length is not None and len(result) != length: + return None + + return result + + +def Hash(data): + return hashlib.sha256(hashlib.sha256(data).digest()).digest() + +def EncodeBase58Check(vchIn): + hash = Hash(vchIn) + return b58encode(vchIn + hash[0:4]) + +def DecodeBase58Check(psz): + vchRet = b58decode(psz, None) + key = vchRet[0:-4] + csum = vchRet[-4:] + hash = Hash(key) + cs32 = hash[0:4] + if cs32 != csum: + return None + else: + return key + +def PrivKeyToSecret(privkey): + return privkey[9:9+32] + +def SecretToASecret(secret): + vchIn = chr(addrtype+128) + secret + return EncodeBase58Check(vchIn) + +def ASecretToSecret(key): + vch = DecodeBase58Check(key) + if vch and vch[0] == chr(addrtype+128): + return vch[1:] + else: + return False + +########### end pywallet functions ####################### + +# URL decode +_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE) +urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x) + + +def int_to_hex(i, length=1): + s = hex(i)[2:].rstrip('L') + s = "0"*(2*length - len(s)) + s + return s.decode('hex')[::-1].encode('hex') + + +# AES +EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) +DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) + + + +# secp256k1, http://www.oid-info.com/get/1.3.132.0.10 +_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL +_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L +_b = 0x0000000000000000000000000000000000000000000000000000000000000007L +_a = 0x0000000000000000000000000000000000000000000000000000000000000000L +_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L +_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L +curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) +generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) +oid_secp256k1 = (1,3,132,0,10) +SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) + + +def filter(s): + out = re.sub('( [^\n]*|)\n','',s) + out = out.replace(' ','') + out = out.replace('\n','') + return out + +def raw_tx( inputs, outputs, for_sig = None ): + s = int_to_hex(1,4) + ' version\n' + s += int_to_hex( len(inputs) ) + ' number of inputs\n' + for i in range(len(inputs)): + _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i] + s += p_hash.decode('hex')[::-1].encode('hex') + ' prev hash\n' + s += int_to_hex(p_index,4) + ' prev index\n' + if for_sig is None: + sig = sig + chr(1) # hashtype + script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig) + script += sig.encode('hex') + ' sig\n' + pubkey = chr(4) + pubkey + script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey) + script += pubkey.encode('hex') + ' pubkey\n' + elif for_sig==i: + script = p_script + ' scriptsig \n' + else: + script='' + s += int_to_hex( len(filter(script))/2 ) + ' script length \n' + s += script + s += "ffffffff" + ' sequence\n' + s += int_to_hex( len(outputs) ) + ' number of outputs\n' + for output in outputs: + addr, amount = output + s += int_to_hex( amount, 8) + ' amount: %d\n'%amount + script = '76a9' # op_dup, op_hash_160 + script += '14' # push 0x14 bytes + script += bc_address_to_hash_160(addr).encode('hex') + script += '88ac' # op_equalverify, op_checksig + s += int_to_hex( len(filter(script))/2 ) + ' script length \n' + s += script + ' script \n' + s += int_to_hex(0,4) # lock time + if for_sig is not None: s += int_to_hex(1, 4) # hash type + return s + + + + +def format_satoshis(x, is_diff=False, num_zeros = 0): + from decimal import Decimal + s = str( Decimal(x) /100000000 ) + if is_diff and x>0: + s = "+" + s + if not '.' in s: s += '.' + p = s.find('.') + s += "0"*( 1 + num_zeros - ( len(s) - p )) + s += " "*( 9 - ( len(s) - p )) + s = " "*( 5 - ( p )) + s + return s + + +from version import ELECTRUM_VERSION, SEED_VERSION +from interface import DEFAULT_SERVERS + + + + +class Wallet: + def __init__(self, gui_callback = lambda: None): + + self.electrum_version = ELECTRUM_VERSION + self.seed_version = SEED_VERSION + self.gui_callback = gui_callback + + self.gap_limit = 5 # configuration + self.fee = 100000 + self.num_zeros = 0 + self.master_public_key = '' + + # saved fields + self.use_encryption = False + self.addresses = [] # receiving addresses visible for user + self.change_addresses = [] # addresses used as change + self.seed = '' # encrypted + self.history = {} + self.labels = {} # labels for addresses and transactions + self.aliases = {} # aliases for addresses + self.authorities = {} # trusted addresses + self.frozen_addresses = [] + + self.receipts = {} # signed URIs + self.receipt = None # next receipt + self.addressbook = [] # outgoing addresses, for payments + + # not saved + self.tx_history = {} + + self.imported_keys = {} + self.remote_url = None + + self.was_updated = True + self.blocks = -1 + self.banner = '' + + # there is a difference between self.up_to_date and self.is_up_to_date() + # self.is_up_to_date() returns true when all requests have been answered and processed + # self.up_to_date is true when the wallet is synchronized (stronger requirement) + self.up_to_date_event = threading.Event() + self.up_to_date_event.clear() + self.up_to_date = False + self.lock = threading.Lock() + self.tx_event = threading.Event() + + self.pick_random_server() + + + + def pick_random_server(self): + self.server = random.choice( DEFAULT_SERVERS ) # random choice when the wallet is created + + def is_up_to_date(self): + return self.interface.responses.empty() and not self.interface.unanswered_requests + + def set_server(self, server): + # raise an error if the format isnt correct + a,b,c = server.split(':') + b = int(b) + assert c in ['t','h','n'] + # set the server + if server != self.server: + self.server = server + self.save() + self.interface.is_connected = False # this exits the polling loop + + def set_path(self, wallet_path): + + if wallet_path is not None: + self.path = wallet_path + else: + # backward compatibility: look for wallet file in the default data directory + if "HOME" in os.environ: + wallet_dir = os.path.join( os.environ["HOME"], '.electrum') + elif "LOCALAPPDATA" in os.environ: + wallet_dir = os.path.join( os.environ["LOCALAPPDATA"], 'Electrum' ) + elif "APPDATA" in os.environ: + wallet_dir = os.path.join( os.environ["APPDATA"], 'Electrum' ) + else: + raise BaseException("No home directory found in environment variables.") + + if not os.path.exists( wallet_dir ): os.mkdir( wallet_dir ) + self.path = os.path.join( wallet_dir, 'electrum.dat' ) + + def import_key(self, keypair, password): + address, key = keypair.split(':') + if not self.is_valid(address): return False + if address in self.all_addresses(): return False + b = ASecretToSecret( key ) + if not b: return False + secexp = int( b.encode('hex'), 16) + private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 ) + # sanity check + public_key = private_key.get_verifying_key() + if not address == public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ): return False + self.imported_keys[address] = self.pw_encode( key, password ) + return True + + def new_seed(self, password): + seed = "%032x"%ecdsa.util.randrange( pow(2,128) ) + #self.init_mpk(seed) + # encrypt + self.seed = self.pw_encode( seed, password ) + + + def init_mpk(self,seed): + # public key + curve = SECP256k1 + secexp = self.stretch_key(seed) + master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + self.master_public_key = master_private_key.get_verifying_key().to_string() + + def all_addresses(self): + return self.addresses + self.change_addresses + self.imported_keys.keys() + + def is_mine(self, address): + return address in self.all_addresses() + + def is_change(self, address): + return address in self.change_addresses + + def is_valid(self,addr): + ADDRESS_RE = re.compile('[1-9A-HJ-NP-Za-km-z]{26,}\\Z') + if not ADDRESS_RE.match(addr): return False + try: + h = bc_address_to_hash_160(addr) + except: + return False + return addr == hash_160_to_bc_address(h) + + def stretch_key(self,seed): + oldseed = seed + for i in range(100000): + seed = hashlib.sha256(seed + oldseed).digest() + return string_to_number( seed ) + + def get_sequence(self,n,for_change): + return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) ) + + def get_private_key(self, address, password): + """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ + order = generator_secp256k1.order() + + if address in self.imported_keys.keys(): + b = self.pw_decode( self.imported_keys[address], password ) + b = ASecretToSecret( b ) + secexp = int( b.encode('hex'), 16) + else: + if address in self.addresses: + n = self.addresses.index(address) + for_change = False + elif address in self.change_addresses: + n = self.change_addresses.index(address) + for_change = True + else: + raise BaseException("unknown address") + try: + seed = self.pw_decode( self.seed, password) + except: + raise BaseException("Invalid password") + secexp = self.stretch_key(seed) + secexp = ( secexp + self.get_sequence(n,for_change) ) % order + + pk = number_to_string(secexp,order) + return pk + + def msg_magic(self, message): + return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message + + def sign_message(self, address, message, password): + private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 ) + public_key = private_key.get_verifying_key() + signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string ) + assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string) + for i in range(4): + sig = base64.b64encode( chr(27+i) + signature ) + try: + self.verify_message( address, sig, message) + return sig + except: + continue + else: + raise BaseException("error: cannot sign message") + + + def verify_message(self, address, signature, message): + """ See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """ + from ecdsa import numbertheory, ellipticcurve, util + import msqr + curve = curve_secp256k1 + G = generator_secp256k1 + order = G.order() + # extract r,s from signature + sig = base64.b64decode(signature) + if len(sig) != 65: raise BaseException("Wrong encoding") + r,s = util.sigdecode_string(sig[1:], order) + recid = ord(sig[0]) - 27 + # 1.1 + x = r + (recid/2) * order + # 1.3 + alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p() + beta = msqr.modular_sqrt(alpha, curve.p()) + y = beta if (beta - recid) % 2 == 0 else curve.p() - beta + # 1.4 the constructor checks that nR is at infinity + R = ellipticcurve.Point(curve, x, y, order) + # 1.5 compute e from message: + h = Hash( self.msg_magic( message ) ) + e = string_to_number(h) + minus_e = -e % order + # 1.6 compute Q = r^-1 (sR - eG) + inv_r = numbertheory.inverse_mod(r,order) + Q = inv_r * ( s * R + minus_e * G ) + public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 ) + # check that Q is the public key + public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string) + # check that we get the original signing address + addr = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ) + # print addr + if address != addr: + print "bad signature" + raise BaseException("Bad signature") + + + def create_new_address(self, for_change): + """ Publickey(type,n) = Master_public_key + H(n|S|type)*point """ + curve = SECP256k1 + n = len(self.change_addresses) if for_change else len(self.addresses) + z = self.get_sequence(n,for_change) + master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key, curve = SECP256k1 ) + pubkey_point = master_public_key.pubkey.point + z*curve.generator + public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 ) + address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() ) + if for_change: + self.change_addresses.append(address) + else: + self.addresses.append(address) + + self.history[address] = [] + print address + return address + + + + def synchronize(self): + if not self.master_public_key: + return [] + + new_addresses = [] + while True: + if self.change_addresses == []: + new_addresses.append( self.create_new_address(True) ) + continue + a = self.change_addresses[-1] + if self.history.get(a): + new_addresses.append( self.create_new_address(True) ) + else: + break + + n = self.gap_limit + while True: + if len(self.addresses) < n: + new_addresses.append( self.create_new_address(False) ) + continue + if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]: + break + else: + new_addresses.append( self.create_new_address(False) ) + + if self.remote_url: + num = self.get_remote_number() + while len(self.addresses) 1 ) or ( len(self.addresses) > self.gap_limit ) + + def fill_addressbook(self): + for tx in self.tx_history.values(): + if tx['value']<0: + for i in tx['outputs']: + if not self.is_mine(i) and i not in self.addressbook: + self.addressbook.append(i) + # redo labels + self.update_tx_labels() + + + def save(self): + s = { + 'seed_version':self.seed_version, + 'use_encryption':self.use_encryption, + 'master_public_key': self.master_public_key.encode('hex'), + 'fee':self.fee, + 'server':self.server, + 'seed':self.seed, + 'addresses':self.addresses, + 'change_addresses':self.change_addresses, + 'history':self.history, + 'labels':self.labels, + 'contacts':self.addressbook, + 'imported_keys':self.imported_keys, + 'aliases':self.aliases, + 'authorities':self.authorities, + 'receipts':self.receipts, + 'num_zeros':self.num_zeros, + 'frozen_addresses':self.frozen_addresses, + } + f = open(self.path,"w") + f.write( repr(s) ) + f.close() + + def read(self): + import interface + + upgrade_msg = """This wallet seed is deprecated. Please run upgrade.py for a diagnostic.""" + self.file_exists = False + try: + f = open(self.path,"r") + data = f.read() + f.close() + except: + return + data = interface.old_to_new(data) + try: + d = ast.literal_eval( data ) + self.seed_version = d.get('seed_version') + self.master_public_key = d.get('master_public_key').decode('hex') + self.use_encryption = d.get('use_encryption') + self.fee = int( d.get('fee') ) + self.seed = d.get('seed') + self.server = d.get('server') + #blocks = d.get('blocks') + self.addresses = d.get('addresses') + self.change_addresses = d.get('change_addresses') + self.history = d.get('history') + self.labels = d.get('labels') + self.addressbook = d.get('contacts') + self.imported_keys = d.get('imported_keys',{}) + self.aliases = d.get('aliases',{}) + self.authorities = d.get('authorities',{}) + self.receipts = d.get('receipts',{}) + self.num_zeros = d.get('num_zeros',0) + self.frozen_addresses = d.get('frozen_addresses',[]) + except: + raise BaseException("cannot read wallet file") + + self.update_tx_history() + + if self.seed_version != SEED_VERSION: + raise BaseException(upgrade_msg) + + if self.remote_url: assert self.master_public_key.encode('hex') == self.get_remote_mpk() + + self.file_exists = True + + + + + def get_addr_balance(self, addr): + assert self.is_mine(addr) + h = self.history.get(addr,[]) + c = u = 0 + for item in h: + v = item['value'] + if item['height']: + c += v + else: + u += v + return c, u + + def get_balance(self): + conf = unconf = 0 + for addr in self.all_addresses(): + c, u = self.get_addr_balance(addr) + conf += c + unconf += u + return conf, unconf + + + def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ): + """ todo: minimize tx size """ + total = 0 + fee = self.fee if fixed_fee is None else fixed_fee + + coins = [] + domain = [from_addr] if from_addr else self.all_addresses() + for i in self.frozen_addresses: + if i in domain: domain.remove(i) + + for addr in domain: + h = self.history.get(addr) + if h is None: continue + for item in h: + if item.get('raw_output_script'): + coins.append( (addr,item)) + + coins = sorted( coins, key = lambda x: x[1]['timestamp'] ) + inputs = [] + for c in coins: + addr, item = c + v = item.get('value') + total += v + inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) ) + fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee + if total >= amount + fee: break + else: + #print "not enough funds: %d %d"%(total, fee) + inputs = [] + return inputs, total, fee + + def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ): + outputs = [ (to_addr, amount) ] + change_amount = total - ( amount + fee ) + if change_amount != 0: + # normally, the update thread should ensure that the last change address is unused + if not change_addr: + change_addr = self.change_addresses[-1] + outputs.append( ( change_addr, change_amount) ) + return outputs + + def sign_inputs( self, inputs, outputs, password ): + s_inputs = [] + for i in range(len(inputs)): + addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i] + private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 ) + public_key = private_key.get_verifying_key() + pubkey = public_key.to_string() + tx = filter( raw_tx( inputs, outputs, for_sig = i ) ) + sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) + assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) + s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) ) + return s_inputs + + def pw_encode(self, s, password): + if password: + secret = Hash(password) + return EncodeAES(secret, s) + else: + return s + + def pw_decode(self, s, password): + if password is not None: + secret = Hash(password) + d = DecodeAES(secret, s) + if s == self.seed: + try: + d.decode('hex') + except: + raise BaseException("Invalid password") + return d + else: + return s + + def get_status(self, address): + h = self.history.get(address) + if not h: + status = None + else: + lastpoint = h[-1] + status = lastpoint['block_hash'] + if status == 'mempool': + status = status + ':%d'% len(h) + return status + + def receive_status_callback(self, addr, status): + with self.lock: + if self.get_status(addr) != status: + #print "updating status for", addr, status + self.interface.get_history(addr) + + def receive_history_callback(self, addr, data): + #print "updating history for", addr + with self.lock: + self.history[addr] = data + self.update_tx_history() + self.save() + + def get_tx_history(self): + lines = self.tx_history.values() + lines = sorted(lines, key=operator.itemgetter("timestamp")) + return lines + + def update_tx_history(self): + self.tx_history= {} + for addr in self.all_addresses(): + h = self.history.get(addr) + if h is None: continue + for tx in h: + tx_hash = tx['tx_hash'] + line = self.tx_history.get(tx_hash) + if not line: + self.tx_history[tx_hash] = copy.copy(tx) + line = self.tx_history.get(tx_hash) + else: + line['value'] += tx['value'] + if line['height'] == 0: + line['timestamp'] = 1e12 + self.update_tx_labels() + + def update_tx_labels(self): + for tx in self.tx_history.values(): + default_label = '' + if tx['value']<0: + for o_addr in tx['outputs']: + if not self.is_change(o_addr): + dest_label = self.labels.get(o_addr) + if dest_label: + default_label = 'to: ' + dest_label + else: + default_label = 'to: ' + o_addr + else: + for o_addr in tx['outputs']: + if self.is_mine(o_addr) and not self.is_change(o_addr): + dest_label = self.labels.get(o_addr) + if dest_label: + default_label = 'at: ' + dest_label + else: + default_label = 'at: ' + o_addr + tx['default_label'] = default_label + + def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None): + if not self.is_valid(to_address): + raise BaseException("Invalid address") + inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr ) + if not inputs: + raise BaseException("Not enough funds") + outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr ) + s_inputs = self.sign_inputs( inputs, outputs, password ) + + tx = filter( raw_tx( s_inputs, outputs ) ) + if to_address not in self.addressbook: + self.addressbook.append(to_address) + if label: + tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex') + self.labels[tx_hash] = label + + return tx + + def sendtx(self, tx): + tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex') + self.tx_event.clear() + self.interface.send([('blockchain.transaction.broadcast', [tx])]) + self.tx_event.wait() + out = self.tx_result + if out != tx_hash: + return False, "error: " + out + if self.receipt: + self.receipts[tx_hash] = self.receipt + self.receipt = None + return True, out + + + def read_alias(self, alias): + # this might not be the right place for this function. + import urllib + + m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias) + m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias) + if m1: + url = 'http://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) + elif m2: + url = 'http://' + alias + '/bitcoin.id' + else: + return '' + try: + lines = urllib.urlopen(url).readlines() + except: + return '' + + # line 0 + line = lines[0].strip().split(':') + if len(line) == 1: + auth_name = None + target = signing_addr = line[0] + else: + target, auth_name, signing_addr, signature = line + msg = "alias:%s:%s:%s"%(alias,target,auth_name) + print msg, signature + self.verify_message(signing_addr, signature, msg) + + # other lines are signed updates + for line in lines[1:]: + line = line.strip() + if not line: continue + line = line.split(':') + previous = target + print repr(line) + target, signature = line + self.verify_message(previous, signature, "alias:%s:%s"%(alias,target)) + + if not self.is_valid(target): + raise BaseException("Invalid bitcoin address") + + return target, signing_addr, auth_name + + def update_password(self, seed, new_password): + if new_password == '': new_password = None + self.use_encryption = (new_password != None) + self.seed = self.pw_encode( seed, new_password) + for k in self.imported_keys.keys(): + a = self.imported_keys[k] + b = self.pw_decode(a, password) + c = self.pw_encode(b, new_password) + self.imported_keys[k] = c + self.save() + + def get_alias(self, alias, interactive = False, show_message=None, question = None): + try: + target, signing_address, auth_name = self.read_alias(alias) + except BaseException, e: + # raise exception if verify fails (verify the chain) + if interactive: + show_message("Alias error: " + e.message) + return + + print target, signing_address, auth_name + + if auth_name is None: + a = self.aliases.get(alias) + if not a: + msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address) + if interactive and question( msg ): + self.aliases[alias] = (signing_address, target) + else: + target = None + else: + if signing_address != a[0]: + msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias + if interactive and question( msg ): + self.aliases[alias] = (signing_address, target) + else: + target = None + else: + if signing_address not in self.authorities.keys(): + msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address) + if interactive and question( msg ): + self.authorities[signing_address] = auth_name + else: + target = None + + if target: + self.aliases[alias] = (signing_address, target) + + return target + + + def parse_url(self, url, show_message, question): + o = url[8:].split('?') + address = o[0] + if len(o)>1: + params = o[1].split('&') + else: + params = [] + + amount = label = message = signature = identity = '' + for p in params: + k,v = p.split('=') + uv = urldecode(v) + if k == 'amount': amount = uv + elif k == 'message': message = uv + elif k == 'label': label = uv + elif k == 'signature': + identity, signature = uv.split(':') + url = url.replace('&%s=%s'%(k,v),'') + else: + print k,v + + if signature: + if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity): + signing_address = self.get_alias(identity, True, show_message, question) + elif self.is_valid(identity): + signing_address = identity + else: + signing_address = None + if not signing_address: + return + try: + self.verify_message(signing_address, signature, url ) + self.receipt = (signing_address, signature, url) + except: + show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.') + address = amount = label = identity = message = '' + + if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address): + payto_address = self.get_alias(address, True, show_message, question) + if payto_address: + address = address + ' <' + payto_address + '>' + + return address, amount, label, message, signature, identity, url + + + def update(self): + self.interface.poke() + self.up_to_date_event.wait() + + + def start_session(self, interface): + self.interface = interface + self.interface.send([('server.banner',[]), ('blockchain.numblocks.subscribe',[]), ('server.peers.subscribe',[])]) + self.interface.subscribe(self.all_addresses()) + + + + diff --git a/mnemonic.py b/mnemonic.py deleted file mode 100644 index 82d2624..0000000 --- a/mnemonic.py +++ /dev/null @@ -1,1689 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2011 thomasv@gitorious -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - - -# list of words from http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry - -words = [ -"like", -"just", -"love", -"know", -"never", -"want", -"time", -"out", -"there", -"make", -"look", -"eye", -"down", -"only", -"think", -"heart", -"back", -"then", -"into", -"about", -"more", -"away", -"still", -"them", -"take", -"thing", -"even", -"through", -"long", -"always", -"world", -"too", -"friend", -"tell", -"try", -"hand", -"thought", -"over", -"here", -"other", -"need", -"smile", -"again", -"much", -"cry", -"been", -"night", -"ever", -"little", -"said", -"end", -"some", -"those", -"around", -"mind", -"people", -"girl", -"leave", -"dream", -"left", -"turn", -"myself", -"give", -"nothing", -"really", -"off", -"before", -"something", -"find", -"walk", -"wish", -"good", -"once", -"place", -"ask", -"stop", -"keep", -"watch", -"seem", -"everything", -"wait", -"got", -"yet", -"made", -"remember", -"start", -"alone", -"run", -"hope", -"maybe", -"believe", -"body", -"hate", -"after", -"close", -"talk", -"stand", -"own", -"each", -"hurt", -"help", -"home", -"god", -"soul", -"new", -"many", -"two", -"inside", -"should", -"true", -"first", -"fear", -"mean", -"better", -"play", -"another", -"gone", -"change", -"use", -"wonder", -"someone", -"hair", -"cold", -"open", -"best", -"any", -"behind", -"happen", -"water", -"dark", -"laugh", -"stay", -"forever", -"name", -"work", -"show", -"sky", -"break", -"came", -"deep", -"door", -"put", -"black", -"together", -"upon", -"happy", -"such", -"great", -"white", -"matter", -"fill", -"past", -"please", -"burn", -"cause", -"enough", -"touch", -"moment", -"soon", -"voice", -"scream", -"anything", -"stare", -"sound", -"red", -"everyone", -"hide", -"kiss", -"truth", -"death", -"beautiful", -"mine", -"blood", -"broken", -"very", -"pass", -"next", -"forget", -"tree", -"wrong", -"air", -"mother", -"understand", -"lip", -"hit", -"wall", -"memory", -"sleep", -"free", -"high", -"realize", -"school", -"might", -"skin", -"sweet", -"perfect", -"blue", -"kill", -"breath", -"dance", -"against", -"fly", -"between", -"grow", -"strong", -"under", -"listen", -"bring", -"sometimes", -"speak", -"pull", -"person", -"become", -"family", -"begin", -"ground", -"real", -"small", -"father", -"sure", -"feet", -"rest", -"young", -"finally", -"land", -"across", -"today", -"different", -"guy", -"line", -"fire", -"reason", -"reach", -"second", -"slowly", -"write", -"eat", -"smell", -"mouth", -"step", -"learn", -"three", -"floor", -"promise", -"breathe", -"darkness", -"push", -"earth", -"guess", -"save", -"song", -"above", -"along", -"both", -"color", -"house", -"almost", -"sorry", -"anymore", -"brother", -"okay", -"dear", -"game", -"fade", -"already", -"apart", -"warm", -"beauty", -"heard", -"notice", -"question", -"shine", -"began", -"piece", -"whole", -"shadow", -"secret", -"street", -"within", -"finger", -"point", -"morning", -"whisper", -"child", -"moon", -"green", -"story", -"glass", -"kid", -"silence", -"since", -"soft", -"yourself", -"empty", -"shall", -"angel", -"answer", -"baby", -"bright", -"dad", -"path", -"worry", -"hour", -"drop", -"follow", -"power", -"war", -"half", -"flow", -"heaven", -"act", -"chance", -"fact", -"least", -"tired", -"children", -"near", -"quite", -"afraid", -"rise", -"sea", -"taste", -"window", -"cover", -"nice", -"trust", -"lot", -"sad", -"cool", -"force", -"peace", -"return", -"blind", -"easy", -"ready", -"roll", -"rose", -"drive", -"held", -"music", -"beneath", -"hang", -"mom", -"paint", -"emotion", -"quiet", -"clear", -"cloud", -"few", -"pretty", -"bird", -"outside", -"paper", -"picture", -"front", -"rock", -"simple", -"anyone", -"meant", -"reality", -"road", -"sense", -"waste", -"bit", -"leaf", -"thank", -"happiness", -"meet", -"men", -"smoke", -"truly", -"decide", -"self", -"age", -"book", -"form", -"alive", -"carry", -"escape", -"damn", -"instead", -"able", -"ice", -"minute", -"throw", -"catch", -"leg", -"ring", -"course", -"goodbye", -"lead", -"poem", -"sick", -"corner", -"desire", -"known", -"problem", -"remind", -"shoulder", -"suppose", -"toward", -"wave", -"drink", -"jump", -"woman", -"pretend", -"sister", -"week", -"human", -"joy", -"crack", -"grey", -"pray", -"surprise", -"dry", -"knee", -"less", -"search", -"bleed", -"caught", -"clean", -"embrace", -"future", -"king", -"son", -"sorrow", -"chest", -"hug", -"remain", -"sat", -"worth", -"blow", -"daddy", -"final", -"parent", -"tight", -"also", -"create", -"lonely", -"safe", -"cross", -"dress", -"evil", -"silent", -"bone", -"fate", -"perhaps", -"anger", -"class", -"scar", -"snow", -"tiny", -"tonight", -"continue", -"control", -"dog", -"edge", -"mirror", -"month", -"suddenly", -"comfort", -"given", -"loud", -"quickly", -"gaze", -"plan", -"rush", -"stone", -"town", -"battle", -"ignore", -"spirit", -"stood", -"stupid", -"yours", -"brown", -"build", -"dust", -"hey", -"kept", -"pay", -"phone", -"twist", -"although", -"ball", -"beyond", -"hidden", -"nose", -"taken", -"fail", -"float", -"pure", -"somehow", -"wash", -"wrap", -"angry", -"cheek", -"creature", -"forgotten", -"heat", -"rip", -"single", -"space", -"special", -"weak", -"whatever", -"yell", -"anyway", -"blame", -"job", -"choose", -"country", -"curse", -"drift", -"echo", -"figure", -"grew", -"laughter", -"neck", -"suffer", -"worse", -"yeah", -"disappear", -"foot", -"forward", -"knife", -"mess", -"somewhere", -"stomach", -"storm", -"beg", -"idea", -"lift", -"offer", -"breeze", -"field", -"five", -"often", -"simply", -"stuck", -"win", -"allow", -"confuse", -"enjoy", -"except", -"flower", -"seek", -"strength", -"calm", -"grin", -"gun", -"heavy", -"hill", -"large", -"ocean", -"shoe", -"sigh", -"straight", -"summer", -"tongue", -"accept", -"crazy", -"everyday", -"exist", -"grass", -"mistake", -"sent", -"shut", -"surround", -"table", -"ache", -"brain", -"destroy", -"heal", -"nature", -"shout", -"sign", -"stain", -"choice", -"doubt", -"glance", -"glow", -"mountain", -"queen", -"stranger", -"throat", -"tomorrow", -"city", -"either", -"fish", -"flame", -"rather", -"shape", -"spin", -"spread", -"ash", -"distance", -"finish", -"image", -"imagine", -"important", -"nobody", -"shatter", -"warmth", -"became", -"feed", -"flesh", -"funny", -"lust", -"shirt", -"trouble", -"yellow", -"attention", -"bare", -"bite", -"money", -"protect", -"amaze", -"appear", -"born", -"choke", -"completely", -"daughter", -"fresh", -"friendship", -"gentle", -"probably", -"six", -"deserve", -"expect", -"grab", -"middle", -"nightmare", -"river", -"thousand", -"weight", -"worst", -"wound", -"barely", -"bottle", -"cream", -"regret", -"relationship", -"stick", -"test", -"crush", -"endless", -"fault", -"itself", -"rule", -"spill", -"art", -"circle", -"join", -"kick", -"mask", -"master", -"passion", -"quick", -"raise", -"smooth", -"unless", -"wander", -"actually", -"broke", -"chair", -"deal", -"favorite", -"gift", -"note", -"number", -"sweat", -"box", -"chill", -"clothes", -"lady", -"mark", -"park", -"poor", -"sadness", -"tie", -"animal", -"belong", -"brush", -"consume", -"dawn", -"forest", -"innocent", -"pen", -"pride", -"stream", -"thick", -"clay", -"complete", -"count", -"draw", -"faith", -"press", -"silver", -"struggle", -"surface", -"taught", -"teach", -"wet", -"bless", -"chase", -"climb", -"enter", -"letter", -"melt", -"metal", -"movie", -"stretch", -"swing", -"vision", -"wife", -"beside", -"crash", -"forgot", -"guide", -"haunt", -"joke", -"knock", -"plant", -"pour", -"prove", -"reveal", -"steal", -"stuff", -"trip", -"wood", -"wrist", -"bother", -"bottom", -"crawl", -"crowd", -"fix", -"forgive", -"frown", -"grace", -"loose", -"lucky", -"party", -"release", -"surely", -"survive", -"teacher", -"gently", -"grip", -"speed", -"suicide", -"travel", -"treat", -"vein", -"written", -"cage", -"chain", -"conversation", -"date", -"enemy", -"however", -"interest", -"million", -"page", -"pink", -"proud", -"sway", -"themselves", -"winter", -"church", -"cruel", -"cup", -"demon", -"experience", -"freedom", -"pair", -"pop", -"purpose", -"respect", -"shoot", -"softly", -"state", -"strange", -"bar", -"birth", -"curl", -"dirt", -"excuse", -"lord", -"lovely", -"monster", -"order", -"pack", -"pants", -"pool", -"scene", -"seven", -"shame", -"slide", -"ugly", -"among", -"blade", -"blonde", -"closet", -"creek", -"deny", -"drug", -"eternity", -"gain", -"grade", -"handle", -"key", -"linger", -"pale", -"prepare", -"swallow", -"swim", -"tremble", -"wheel", -"won", -"cast", -"cigarette", -"claim", -"college", -"direction", -"dirty", -"gather", -"ghost", -"hundred", -"loss", -"lung", -"orange", -"present", -"swear", -"swirl", -"twice", -"wild", -"bitter", -"blanket", -"doctor", -"everywhere", -"flash", -"grown", -"knowledge", -"numb", -"pressure", -"radio", -"repeat", -"ruin", -"spend", -"unknown", -"buy", -"clock", -"devil", -"early", -"false", -"fantasy", -"pound", -"precious", -"refuse", -"sheet", -"teeth", -"welcome", -"add", -"ahead", -"block", -"bury", -"caress", -"content", -"depth", -"despite", -"distant", -"marry", -"purple", -"threw", -"whenever", -"bomb", -"dull", -"easily", -"grasp", -"hospital", -"innocence", -"normal", -"receive", -"reply", -"rhyme", -"shade", -"someday", -"sword", -"toe", -"visit", -"asleep", -"bought", -"center", -"consider", -"flat", -"hero", -"history", -"ink", -"insane", -"muscle", -"mystery", -"pocket", -"reflection", -"shove", -"silently", -"smart", -"soldier", -"spot", -"stress", -"train", -"type", -"view", -"whether", -"bus", -"energy", -"explain", -"holy", -"hunger", -"inch", -"magic", -"mix", -"noise", -"nowhere", -"prayer", -"presence", -"shock", -"snap", -"spider", -"study", -"thunder", -"trail", -"admit", -"agree", -"bag", -"bang", -"bound", -"butterfly", -"cute", -"exactly", -"explode", -"familiar", -"fold", -"further", -"pierce", -"reflect", -"scent", -"selfish", -"sharp", -"sink", -"spring", -"stumble", -"universe", -"weep", -"women", -"wonderful", -"action", -"ancient", -"attempt", -"avoid", -"birthday", -"branch", -"chocolate", -"core", -"depress", -"drunk", -"especially", -"focus", -"fruit", -"honest", -"match", -"palm", -"perfectly", -"pillow", -"pity", -"poison", -"roar", -"shift", -"slightly", -"thump", -"truck", -"tune", -"twenty", -"unable", -"wipe", -"wrote", -"coat", -"constant", -"dinner", -"drove", -"egg", -"eternal", -"flight", -"flood", -"frame", -"freak", -"gasp", -"glad", -"hollow", -"motion", -"peer", -"plastic", -"root", -"screen", -"season", -"sting", -"strike", -"team", -"unlike", -"victim", -"volume", -"warn", -"weird", -"attack", -"await", -"awake", -"built", -"charm", -"crave", -"despair", -"fought", -"grant", -"grief", -"horse", -"limit", -"message", -"ripple", -"sanity", -"scatter", -"serve", -"split", -"string", -"trick", -"annoy", -"blur", -"boat", -"brave", -"clearly", -"cling", -"connect", -"fist", -"forth", -"imagination", -"iron", -"jock", -"judge", -"lesson", -"milk", -"misery", -"nail", -"naked", -"ourselves", -"poet", -"possible", -"princess", -"sail", -"size", -"snake", -"society", -"stroke", -"torture", -"toss", -"trace", -"wise", -"bloom", -"bullet", -"cell", -"check", -"cost", -"darling", -"during", -"footstep", -"fragile", -"hallway", -"hardly", -"horizon", -"invisible", -"journey", -"midnight", -"mud", -"nod", -"pause", -"relax", -"shiver", -"sudden", -"value", -"youth", -"abuse", -"admire", -"blink", -"breast", -"bruise", -"constantly", -"couple", -"creep", -"curve", -"difference", -"dumb", -"emptiness", -"gotta", -"honor", -"plain", -"planet", -"recall", -"rub", -"ship", -"slam", -"soar", -"somebody", -"tightly", -"weather", -"adore", -"approach", -"bond", -"bread", -"burst", -"candle", -"coffee", -"cousin", -"crime", -"desert", -"flutter", -"frozen", -"grand", -"heel", -"hello", -"language", -"level", -"movement", -"pleasure", -"powerful", -"random", -"rhythm", -"settle", -"silly", -"slap", -"sort", -"spoken", -"steel", -"threaten", -"tumble", -"upset", -"aside", -"awkward", -"bee", -"blank", -"board", -"button", -"card", -"carefully", -"complain", -"crap", -"deeply", -"discover", -"drag", -"dread", -"effort", -"entire", -"fairy", -"giant", -"gotten", -"greet", -"illusion", -"jeans", -"leap", -"liquid", -"march", -"mend", -"nervous", -"nine", -"replace", -"rope", -"spine", -"stole", -"terror", -"accident", -"apple", -"balance", -"boom", -"childhood", -"collect", -"demand", -"depression", -"eventually", -"faint", -"glare", -"goal", -"group", -"honey", -"kitchen", -"laid", -"limb", -"machine", -"mere", -"mold", -"murder", -"nerve", -"painful", -"poetry", -"prince", -"rabbit", -"shelter", -"shore", -"shower", -"soothe", -"stair", -"steady", -"sunlight", -"tangle", -"tease", -"treasure", -"uncle", -"begun", -"bliss", -"canvas", -"cheer", -"claw", -"clutch", -"commit", -"crimson", -"crystal", -"delight", -"doll", -"existence", -"express", -"fog", -"football", -"gay", -"goose", -"guard", -"hatred", -"illuminate", -"mass", -"math", -"mourn", -"rich", -"rough", -"skip", -"stir", -"student", -"style", -"support", -"thorn", -"tough", -"yard", -"yearn", -"yesterday", -"advice", -"appreciate", -"autumn", -"bank", -"beam", -"bowl", -"capture", -"carve", -"collapse", -"confusion", -"creation", -"dove", -"feather", -"girlfriend", -"glory", -"government", -"harsh", -"hop", -"inner", -"loser", -"moonlight", -"neighbor", -"neither", -"peach", -"pig", -"praise", -"screw", -"shield", -"shimmer", -"sneak", -"stab", -"subject", -"throughout", -"thrown", -"tower", -"twirl", -"wow", -"army", -"arrive", -"bathroom", -"bump", -"cease", -"cookie", -"couch", -"courage", -"dim", -"guilt", -"howl", -"hum", -"husband", -"insult", -"led", -"lunch", -"mock", -"mostly", -"natural", -"nearly", -"needle", -"nerd", -"peaceful", -"perfection", -"pile", -"price", -"remove", -"roam", -"sanctuary", -"serious", -"shiny", -"shook", -"sob", -"stolen", -"tap", -"vain", -"void", -"warrior", -"wrinkle", -"affection", -"apologize", -"blossom", -"bounce", -"bridge", -"cheap", -"crumble", -"decision", -"descend", -"desperately", -"dig", -"dot", -"flip", -"frighten", -"heartbeat", -"huge", -"lazy", -"lick", -"odd", -"opinion", -"process", -"puzzle", -"quietly", -"retreat", -"score", -"sentence", -"separate", -"situation", -"skill", -"soak", -"square", -"stray", -"taint", -"task", -"tide", -"underneath", -"veil", -"whistle", -"anywhere", -"bedroom", -"bid", -"bloody", -"burden", -"careful", -"compare", -"concern", -"curtain", -"decay", -"defeat", -"describe", -"double", -"dreamer", -"driver", -"dwell", -"evening", -"flare", -"flicker", -"grandma", -"guitar", -"harm", -"horrible", -"hungry", -"indeed", -"lace", -"melody", -"monkey", -"nation", -"object", -"obviously", -"rainbow", -"salt", -"scratch", -"shown", -"shy", -"stage", -"stun", -"third", -"tickle", -"useless", -"weakness", -"worship", -"worthless", -"afternoon", -"beard", -"boyfriend", -"bubble", -"busy", -"certain", -"chin", -"concrete", -"desk", -"diamond", -"doom", -"drawn", -"due", -"felicity", -"freeze", -"frost", -"garden", -"glide", -"harmony", -"hopefully", -"hunt", -"jealous", -"lightning", -"mama", -"mercy", -"peel", -"physical", -"position", -"pulse", -"punch", -"quit", -"rant", -"respond", -"salty", -"sane", -"satisfy", -"savior", -"sheep", -"slept", -"social", -"sport", -"tuck", -"utter", -"valley", -"wolf", -"aim", -"alas", -"alter", -"arrow", -"awaken", -"beaten", -"belief", -"brand", -"ceiling", -"cheese", -"clue", -"confidence", -"connection", -"daily", -"disguise", -"eager", -"erase", -"essence", -"everytime", -"expression", -"fan", -"flag", -"flirt", -"foul", -"fur", -"giggle", -"glorious", -"ignorance", -"law", -"lifeless", -"measure", -"mighty", -"muse", -"north", -"opposite", -"paradise", -"patience", -"patient", -"pencil", -"petal", -"plate", -"ponder", -"possibly", -"practice", -"slice", -"spell", -"stock", -"strife", -"strip", -"suffocate", -"suit", -"tender", -"tool", -"trade", -"velvet", -"verse", -"waist", -"witch", -"aunt", -"bench", -"bold", -"cap", -"certainly", -"click", -"companion", -"creator", -"dart", -"delicate", -"determine", -"dish", -"dragon", -"drama", -"drum", -"dude", -"everybody", -"feast", -"forehead", -"former", -"fright", -"fully", -"gas", -"hook", -"hurl", -"invite", -"juice", -"manage", -"moral", -"possess", -"raw", -"rebel", -"royal", -"scale", -"scary", -"several", -"slight", -"stubborn", -"swell", -"talent", -"tea", -"terrible", -"thread", -"torment", -"trickle", -"usually", -"vast", -"violence", -"weave", -"acid", -"agony", -"ashamed", -"awe", -"belly", -"blend", -"blush", -"character", -"cheat", -"common", -"company", -"coward", -"creak", -"danger", -"deadly", -"defense", -"define", -"depend", -"desperate", -"destination", -"dew", -"duck", -"dusty", -"embarrass", -"engine", -"example", -"explore", -"foe", -"freely", -"frustrate", -"generation", -"glove", -"guilty", -"health", -"hurry", -"idiot", -"impossible", -"inhale", -"jaw", -"kingdom", -"mention", -"mist", -"moan", -"mumble", -"mutter", -"observe", -"ode", -"pathetic", -"pattern", -"pie", -"prefer", -"puff", -"rape", -"rare", -"revenge", -"rude", -"scrape", -"spiral", -"squeeze", -"strain", -"sunset", -"suspend", -"sympathy", -"thigh", -"throne", -"total", -"unseen", -"weapon", -"weary" -] - - - -n = 1626 - -# Note about US patent no 5892470: Here each word does not represent a given digit. -# Instead, the digit represented by a word is variable, it depends on the previous word. - -def mn_encode( message ): - out = [] - for i in range(len(message)/8): - word = message[8*i:8*i+8] - x = int(word, 16) - w1 = (x%n) - w2 = ((x/n) + w1)%n - w3 = ((x/n/n) + w2)%n - out += [ words[w1], words[w2], words[w3] ] - return out - -def mn_decode( wlist ): - out = '' - for i in range(len(wlist)/3): - word1, word2, word3 = wlist[3*i:3*i+3] - w1 = words.index(word1) - w2 = (words.index(word2))%n - w3 = (words.index(word3))%n - x = w1 +n*((w2-w1)%n) +n*n*((w3-w2)%n) - out += '%08x'%x - return out - - -if __name__ == '__main__': - import sys - if len( sys.argv ) == 1: - print 'I need arguments: a hex string to encode, or a list of words to decode' - elif len( sys.argv ) == 2: - print ' '.join(mn_encode(sys.argv[1])) - else: - print mn_decode(sys.argv[1:]) diff --git a/msqr.py b/msqr.py deleted file mode 100644 index da3c6fd..0000000 --- a/msqr.py +++ /dev/null @@ -1,94 +0,0 @@ -# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ - -def modular_sqrt(a, p): - """ Find a quadratic residue (mod p) of 'a'. p - must be an odd prime. - - Solve the congruence of the form: - x^2 = a (mod p) - And returns x. Note that p - x is also a root. - - 0 is returned is no square root exists for - these a and p. - - The Tonelli-Shanks algorithm is used (except - for some simple cases in which the solution - is known from an identity). This algorithm - runs in polynomial time (unless the - generalized Riemann hypothesis is false). - """ - # Simple cases - # - if legendre_symbol(a, p) != 1: - return 0 - elif a == 0: - return 0 - elif p == 2: - return p - elif p % 4 == 3: - return pow(a, (p + 1) / 4, p) - - # Partition p-1 to s * 2^e for an odd s (i.e. - # reduce all the powers of 2 from p-1) - # - s = p - 1 - e = 0 - while s % 2 == 0: - s /= 2 - e += 1 - - # Find some 'n' with a legendre symbol n|p = -1. - # Shouldn't take long. - # - n = 2 - while legendre_symbol(n, p) != -1: - n += 1 - - # Here be dragons! - # Read the paper "Square roots from 1; 24, 51, - # 10 to Dan Shanks" by Ezra Brown for more - # information - # - - # x is a guess of the square root that gets better - # with each iteration. - # b is the "fudge factor" - by how much we're off - # with the guess. The invariant x^2 = ab (mod p) - # is maintained throughout the loop. - # g is used for successive powers of n to update - # both a and b - # r is the exponent - decreases with each update - # - x = pow(a, (s + 1) / 2, p) - b = pow(a, s, p) - g = pow(n, s, p) - r = e - - while True: - t = b - m = 0 - for m in xrange(r): - if t == 1: - break - t = pow(t, 2, p) - - if m == 0: - return x - - gs = pow(g, 2 ** (r - m - 1), p) - g = (gs * gs) % p - x = (x * gs) % p - b = (b * g) % p - r = m - -def legendre_symbol(a, p): - """ Compute the Legendre symbol a|p using - Euler's criterion. p is a prime, a is - relatively prime to p (if p divides - a, then a|p = 0) - - Returns 1 if a has a square root modulo - p, -1 otherwise. - """ - ls = pow(a, (p - 1) / 2, p) - return -1 if ls == p - 1 else ls diff --git a/peers b/peers index 0b5d1e0..d7bdcd3 100755 --- a/peers +++ b/peers @@ -1,8 +1,8 @@ #!/usr/bin/env python -import interface +from electrum import TcpStratumInterface -i = interface.TcpStratumInterface('ecdsa.org', 50001) +i = TcpStratumInterface('ecdsa.org', 50001) i.start() i.send([('server.peers.subscribe',[])]) diff --git a/pyqrnative.py b/pyqrnative.py deleted file mode 100644 index 7f9c750..0000000 --- a/pyqrnative.py +++ /dev/null @@ -1,962 +0,0 @@ -import math - -#from PIL import Image, ImageDraw - -#QRCode for Python -# -#Ported from the Javascript library by Sam Curren -# -#QRCode for Javascript -#http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/qrcode.js -# -#Copyright (c) 2009 Kazuhiko Arase -# -#URL: http://www.d-project.com/ -# -#Licensed under the MIT license: -# http://www.opensource.org/licenses/mit-license.php -# -# The word "QR Code" is registered trademark of -# DENSO WAVE INCORPORATED -# http://www.denso-wave.com/qrcode/faqpatent-e.html - - -class QR8bitByte: - - def __init__(self, data): - self.mode = QRMode.MODE_8BIT_BYTE - self.data = data - - def getLength(self): - return len(self.data) - - def write(self, buffer): - for i in range(len(self.data)): - #// not JIS ... - buffer.put(ord(self.data[i]), 8) - def __repr__(self): - return self.data - -class QRCode: - def __init__(self, typeNumber, errorCorrectLevel): - self.typeNumber = typeNumber - self.errorCorrectLevel = errorCorrectLevel - self.modules = None - self.moduleCount = 0 - self.dataCache = None - self.dataList = [] - def addData(self, data): - newData = QR8bitByte(data) - self.dataList.append(newData) - self.dataCache = None - def isDark(self, row, col): - if (row < 0 or self.moduleCount <= row or col < 0 or self.moduleCount <= col): - raise Exception("%s,%s - %s" % (row, col, self.moduleCount)) - return self.modules[row][col] - def getModuleCount(self): - return self.moduleCount - def make(self): - self.makeImpl(False, self.getBestMaskPattern() ) - def makeImpl(self, test, maskPattern): - - self.moduleCount = self.typeNumber * 4 + 17 - self.modules = [None for x in range(self.moduleCount)] - - for row in range(self.moduleCount): - - self.modules[row] = [None for x in range(self.moduleCount)] - - for col in range(self.moduleCount): - self.modules[row][col] = None #//(col + row) % 3; - - self.setupPositionProbePattern(0, 0) - self.setupPositionProbePattern(self.moduleCount - 7, 0) - self.setupPositionProbePattern(0, self.moduleCount - 7) - self.setupPositionAdjustPattern() - self.setupTimingPattern() - self.setupTypeInfo(test, maskPattern) - - if (self.typeNumber >= 7): - self.setupTypeNumber(test) - - if (self.dataCache == None): - self.dataCache = QRCode.createData(self.typeNumber, self.errorCorrectLevel, self.dataList) - self.mapData(self.dataCache, maskPattern) - - def setupPositionProbePattern(self, row, col): - - for r in range(-1, 8): - - if (row + r <= -1 or self.moduleCount <= row + r): continue - - for c in range(-1, 8): - - if (col + c <= -1 or self.moduleCount <= col + c): continue - - if ( (0 <= r and r <= 6 and (c == 0 or c == 6) ) - or (0 <= c and c <= 6 and (r == 0 or r == 6) ) - or (2 <= r and r <= 4 and 2 <= c and c <= 4) ): - self.modules[row + r][col + c] = True; - else: - self.modules[row + r][col + c] = False; - - def getBestMaskPattern(self): - - minLostPoint = 0 - pattern = 0 - - for i in range(8): - - self.makeImpl(True, i); - - lostPoint = QRUtil.getLostPoint(self); - - if (i == 0 or minLostPoint > lostPoint): - minLostPoint = lostPoint - pattern = i - - return pattern - - - def setupTimingPattern(self): - - for r in range(8, self.moduleCount - 8): - if (self.modules[r][6] != None): - continue - self.modules[r][6] = (r % 2 == 0) - - for c in range(8, self.moduleCount - 8): - if (self.modules[6][c] != None): - continue - self.modules[6][c] = (c % 2 == 0) - - def setupPositionAdjustPattern(self): - - pos = QRUtil.getPatternPosition(self.typeNumber) - - for i in range(len(pos)): - - for j in range(len(pos)): - - row = pos[i] - col = pos[j] - - if (self.modules[row][col] != None): - continue - - for r in range(-2, 3): - - for c in range(-2, 3): - - if (r == -2 or r == 2 or c == -2 or c == 2 or (r == 0 and c == 0) ): - self.modules[row + r][col + c] = True - else: - self.modules[row + r][col + c] = False - - def setupTypeNumber(self, test): - - bits = QRUtil.getBCHTypeNumber(self.typeNumber) - - for i in range(18): - mod = (not test and ( (bits >> i) & 1) == 1) - self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = mod; - - for i in range(18): - mod = (not test and ( (bits >> i) & 1) == 1) - self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = mod; - - def setupTypeInfo(self, test, maskPattern): - - data = (self.errorCorrectLevel << 3) | maskPattern - bits = QRUtil.getBCHTypeInfo(data) - - #// vertical - for i in range(15): - - mod = (not test and ( (bits >> i) & 1) == 1) - - if (i < 6): - self.modules[i][8] = mod - elif (i < 8): - self.modules[i + 1][8] = mod - else: - self.modules[self.moduleCount - 15 + i][8] = mod - - #// horizontal - for i in range(15): - - mod = (not test and ( (bits >> i) & 1) == 1); - - if (i < 8): - self.modules[8][self.moduleCount - i - 1] = mod - elif (i < 9): - self.modules[8][15 - i - 1 + 1] = mod - else: - self.modules[8][15 - i - 1] = mod - - #// fixed module - self.modules[self.moduleCount - 8][8] = (not test) - - def mapData(self, data, maskPattern): - - inc = -1 - row = self.moduleCount - 1 - bitIndex = 7 - byteIndex = 0 - - for col in range(self.moduleCount - 1, 0, -2): - - if (col == 6): col-=1 - - while (True): - - for c in range(2): - - if (self.modules[row][col - c] == None): - - dark = False - - if (byteIndex < len(data)): - dark = ( ( (data[byteIndex] >> bitIndex) & 1) == 1) - - mask = QRUtil.getMask(maskPattern, row, col - c) - - if (mask): - dark = not dark - - self.modules[row][col - c] = dark - bitIndex-=1 - - if (bitIndex == -1): - byteIndex+=1 - bitIndex = 7 - - row += inc - - if (row < 0 or self.moduleCount <= row): - row -= inc - inc = -inc - break - PAD0 = 0xEC - PAD1 = 0x11 - - @staticmethod - def createData(typeNumber, errorCorrectLevel, dataList): - - rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel) - - buffer = QRBitBuffer(); - - for i in range(len(dataList)): - data = dataList[i] - buffer.put(data.mode, 4) - buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber) ) - data.write(buffer) - - #// calc num max data. - totalDataCount = 0; - for i in range(len(rsBlocks)): - totalDataCount += rsBlocks[i].dataCount - - if (buffer.getLengthInBits() > totalDataCount * 8): - raise Exception("code length overflow. (" - + buffer.getLengthInBits() - + ">" - + totalDataCount * 8 - + ")") - - #// end code - if (buffer.getLengthInBits() + 4 <= totalDataCount * 8): - buffer.put(0, 4) - - #// padding - while (buffer.getLengthInBits() % 8 != 0): - buffer.putBit(False) - - #// padding - while (True): - - if (buffer.getLengthInBits() >= totalDataCount * 8): - break - buffer.put(QRCode.PAD0, 8) - - if (buffer.getLengthInBits() >= totalDataCount * 8): - break - buffer.put(QRCode.PAD1, 8) - - return QRCode.createBytes(buffer, rsBlocks) - - @staticmethod - def createBytes(buffer, rsBlocks): - - offset = 0 - - maxDcCount = 0 - maxEcCount = 0 - - dcdata = [0 for x in range(len(rsBlocks))] - ecdata = [0 for x in range(len(rsBlocks))] - - for r in range(len(rsBlocks)): - - dcCount = rsBlocks[r].dataCount - ecCount = rsBlocks[r].totalCount - dcCount - - maxDcCount = max(maxDcCount, dcCount) - maxEcCount = max(maxEcCount, ecCount) - - dcdata[r] = [0 for x in range(dcCount)] - - for i in range(len(dcdata[r])): - dcdata[r][i] = 0xff & buffer.buffer[i + offset] - offset += dcCount - - rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) - rawPoly = QRPolynomial(dcdata[r], rsPoly.getLength() - 1) - - modPoly = rawPoly.mod(rsPoly) - ecdata[r] = [0 for x in range(rsPoly.getLength()-1)] - for i in range(len(ecdata[r])): - modIndex = i + modPoly.getLength() - len(ecdata[r]) - if (modIndex >= 0): - ecdata[r][i] = modPoly.get(modIndex) - else: - ecdata[r][i] = 0 - - totalCodeCount = 0 - for i in range(len(rsBlocks)): - totalCodeCount += rsBlocks[i].totalCount - - data = [None for x in range(totalCodeCount)] - index = 0 - - for i in range(maxDcCount): - for r in range(len(rsBlocks)): - if (i < len(dcdata[r])): - data[index] = dcdata[r][i] - index+=1 - - for i in range(maxEcCount): - for r in range(len(rsBlocks)): - if (i < len(ecdata[r])): - data[index] = ecdata[r][i] - index+=1 - - return data - - -class QRMode: - MODE_NUMBER = 1 << 0 - MODE_ALPHA_NUM = 1 << 1 - MODE_8BIT_BYTE = 1 << 2 - MODE_KANJI = 1 << 3 - -class QRErrorCorrectLevel: - L = 1 - M = 0 - Q = 3 - H = 2 - -class QRMaskPattern: - PATTERN000 = 0 - PATTERN001 = 1 - PATTERN010 = 2 - PATTERN011 = 3 - PATTERN100 = 4 - PATTERN101 = 5 - PATTERN110 = 6 - PATTERN111 = 7 - -class QRUtil(object): - PATTERN_POSITION_TABLE = [ - [], - [6, 18], - [6, 22], - [6, 26], - [6, 30], - [6, 34], - [6, 22, 38], - [6, 24, 42], - [6, 26, 46], - [6, 28, 50], - [6, 30, 54], - [6, 32, 58], - [6, 34, 62], - [6, 26, 46, 66], - [6, 26, 48, 70], - [6, 26, 50, 74], - [6, 30, 54, 78], - [6, 30, 56, 82], - [6, 30, 58, 86], - [6, 34, 62, 90], - [6, 28, 50, 72, 94], - [6, 26, 50, 74, 98], - [6, 30, 54, 78, 102], - [6, 28, 54, 80, 106], - [6, 32, 58, 84, 110], - [6, 30, 58, 86, 114], - [6, 34, 62, 90, 118], - [6, 26, 50, 74, 98, 122], - [6, 30, 54, 78, 102, 126], - [6, 26, 52, 78, 104, 130], - [6, 30, 56, 82, 108, 134], - [6, 34, 60, 86, 112, 138], - [6, 30, 58, 86, 114, 142], - [6, 34, 62, 90, 118, 146], - [6, 30, 54, 78, 102, 126, 150], - [6, 24, 50, 76, 102, 128, 154], - [6, 28, 54, 80, 106, 132, 158], - [6, 32, 58, 84, 110, 136, 162], - [6, 26, 54, 82, 110, 138, 166], - [6, 30, 58, 86, 114, 142, 170] - ] - - G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0) - G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0) - G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) - - @staticmethod - def getBCHTypeInfo(data): - d = data << 10; - while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0): - d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) ) ) - - return ( (data << 10) | d) ^ QRUtil.G15_MASK - @staticmethod - def getBCHTypeNumber(data): - d = data << 12; - while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0): - d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) ) ) - return (data << 12) | d - @staticmethod - def getBCHDigit(data): - digit = 0; - while (data != 0): - digit += 1 - data >>= 1 - return digit - @staticmethod - def getPatternPosition(typeNumber): - return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1] - @staticmethod - def getMask(maskPattern, i, j): - if maskPattern == QRMaskPattern.PATTERN000 : return (i + j) % 2 == 0 - if maskPattern == QRMaskPattern.PATTERN001 : return i % 2 == 0 - if maskPattern == QRMaskPattern.PATTERN010 : return j % 3 == 0 - if maskPattern == QRMaskPattern.PATTERN011 : return (i + j) % 3 == 0 - if maskPattern == QRMaskPattern.PATTERN100 : return (math.floor(i / 2) + math.floor(j / 3) ) % 2 == 0 - if maskPattern == QRMaskPattern.PATTERN101 : return (i * j) % 2 + (i * j) % 3 == 0 - if maskPattern == QRMaskPattern.PATTERN110 : return ( (i * j) % 2 + (i * j) % 3) % 2 == 0 - if maskPattern == QRMaskPattern.PATTERN111 : return ( (i * j) % 3 + (i + j) % 2) % 2 == 0 - raise Exception("bad maskPattern:" + maskPattern); - @staticmethod - def getErrorCorrectPolynomial(errorCorrectLength): - a = QRPolynomial([1], 0); - for i in range(errorCorrectLength): - a = a.multiply(QRPolynomial([1, QRMath.gexp(i)], 0) ) - return a - @staticmethod - def getLengthInBits(mode, type): - - if 1 <= type and type < 10: - - #// 1 - 9 - - if mode == QRMode.MODE_NUMBER : return 10 - if mode == QRMode.MODE_ALPHA_NUM : return 9 - if mode == QRMode.MODE_8BIT_BYTE : return 8 - if mode == QRMode.MODE_KANJI : return 8 - raise Exception("mode:" + mode) - - elif (type < 27): - - #// 10 - 26 - - if mode == QRMode.MODE_NUMBER : return 12 - if mode == QRMode.MODE_ALPHA_NUM : return 11 - if mode == QRMode.MODE_8BIT_BYTE : return 16 - if mode == QRMode.MODE_KANJI : return 10 - raise Exception("mode:" + mode) - - elif (type < 41): - - #// 27 - 40 - - if mode == QRMode.MODE_NUMBER : return 14 - if mode == QRMode.MODE_ALPHA_NUM : return 13 - if mode == QRMode.MODE_8BIT_BYTE : return 16 - if mode == QRMode.MODE_KANJI : return 12 - raise Exception("mode:" + mode) - - else: - raise Exception("type:" + type) - @staticmethod - def getLostPoint(qrCode): - - moduleCount = qrCode.getModuleCount(); - - lostPoint = 0; - - #// LEVEL1 - - for row in range(moduleCount): - - for col in range(moduleCount): - - sameCount = 0; - dark = qrCode.isDark(row, col); - - for r in range(-1, 2): - - if (row + r < 0 or moduleCount <= row + r): - continue - - for c in range(-1, 2): - - if (col + c < 0 or moduleCount <= col + c): - continue - if (r == 0 and c == 0): - continue - - if (dark == qrCode.isDark(row + r, col + c) ): - sameCount+=1 - if (sameCount > 5): - lostPoint += (3 + sameCount - 5) - - #// LEVEL2 - - for row in range(moduleCount - 1): - for col in range(moduleCount - 1): - count = 0; - if (qrCode.isDark(row, col ) ): count+=1 - if (qrCode.isDark(row + 1, col ) ): count+=1 - if (qrCode.isDark(row, col + 1) ): count+=1 - if (qrCode.isDark(row + 1, col + 1) ): count+=1 - if (count == 0 or count == 4): - lostPoint += 3 - - #// LEVEL3 - - for row in range(moduleCount): - for col in range(moduleCount - 6): - if (qrCode.isDark(row, col) - and not qrCode.isDark(row, col + 1) - and qrCode.isDark(row, col + 2) - and qrCode.isDark(row, col + 3) - and qrCode.isDark(row, col + 4) - and not qrCode.isDark(row, col + 5) - and qrCode.isDark(row, col + 6) ): - lostPoint += 40 - - for col in range(moduleCount): - for row in range(moduleCount - 6): - if (qrCode.isDark(row, col) - and not qrCode.isDark(row + 1, col) - and qrCode.isDark(row + 2, col) - and qrCode.isDark(row + 3, col) - and qrCode.isDark(row + 4, col) - and not qrCode.isDark(row + 5, col) - and qrCode.isDark(row + 6, col) ): - lostPoint += 40 - - #// LEVEL4 - - darkCount = 0; - - for col in range(moduleCount): - for row in range(moduleCount): - if (qrCode.isDark(row, col) ): - darkCount+=1 - - ratio = abs(100 * darkCount / moduleCount / moduleCount - 50) / 5 - lostPoint += ratio * 10 - - return lostPoint - -class QRMath: - - @staticmethod - def glog(n): - if (n < 1): - raise Exception("glog(" + n + ")") - return LOG_TABLE[n]; - @staticmethod - def gexp(n): - while n < 0: - n += 255 - while n >= 256: - n -= 255 - return EXP_TABLE[n]; - -EXP_TABLE = [x for x in range(256)] - -LOG_TABLE = [x for x in range(256)] - -for i in range(8): - EXP_TABLE[i] = 1 << i; - -for i in range(8, 256): - EXP_TABLE[i] = EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8] - -for i in range(255): - LOG_TABLE[EXP_TABLE[i] ] = i - -class QRPolynomial: - - def __init__(self, num, shift): - - if (len(num) == 0): - raise Exception(num.length + "/" + shift) - - offset = 0 - - while offset < len(num) and num[offset] == 0: - offset += 1 - - self.num = [0 for x in range(len(num)-offset+shift)] - for i in range(len(num) - offset): - self.num[i] = num[i + offset] - - - def get(self, index): - return self.num[index] - def getLength(self): - return len(self.num) - def multiply(self, e): - num = [0 for x in range(self.getLength() + e.getLength() - 1)]; - - for i in range(self.getLength()): - for j in range(e.getLength()): - num[i + j] ^= QRMath.gexp(QRMath.glog(self.get(i) ) + QRMath.glog(e.get(j) ) ) - - return QRPolynomial(num, 0); - def mod(self, e): - - if (self.getLength() - e.getLength() < 0): - return self; - - ratio = QRMath.glog(self.get(0) ) - QRMath.glog(e.get(0) ) - - num = [0 for x in range(self.getLength())] - - for i in range(self.getLength()): - num[i] = self.get(i); - - for i in range(e.getLength()): - num[i] ^= QRMath.gexp(QRMath.glog(e.get(i) ) + ratio) - - # recursive call - return QRPolynomial(num, 0).mod(e); - -class QRRSBlock: - - RS_BLOCK_TABLE = [ - - #// L - #// M - #// Q - #// H - - #// 1 - [1, 26, 19], - [1, 26, 16], - [1, 26, 13], - [1, 26, 9], - - #// 2 - [1, 44, 34], - [1, 44, 28], - [1, 44, 22], - [1, 44, 16], - - #// 3 - [1, 70, 55], - [1, 70, 44], - [2, 35, 17], - [2, 35, 13], - - #// 4 - [1, 100, 80], - [2, 50, 32], - [2, 50, 24], - [4, 25, 9], - - #// 5 - [1, 134, 108], - [2, 67, 43], - [2, 33, 15, 2, 34, 16], - [2, 33, 11, 2, 34, 12], - - #// 6 - [2, 86, 68], - [4, 43, 27], - [4, 43, 19], - [4, 43, 15], - - #// 7 - [2, 98, 78], - [4, 49, 31], - [2, 32, 14, 4, 33, 15], - [4, 39, 13, 1, 40, 14], - - #// 8 - [2, 121, 97], - [2, 60, 38, 2, 61, 39], - [4, 40, 18, 2, 41, 19], - [4, 40, 14, 2, 41, 15], - - #// 9 - [2, 146, 116], - [3, 58, 36, 2, 59, 37], - [4, 36, 16, 4, 37, 17], - [4, 36, 12, 4, 37, 13], - - #// 10 - [2, 86, 68, 2, 87, 69], - [4, 69, 43, 1, 70, 44], - [6, 43, 19, 2, 44, 20], - [6, 43, 15, 2, 44, 16], - - # 11 - [4, 101, 81], - [1, 80, 50, 4, 81, 51], - [4, 50, 22, 4, 51, 23], - [3, 36, 12, 8, 37, 13], - - # 12 - [2, 116, 92, 2, 117, 93], - [6, 58, 36, 2, 59, 37], - [4, 46, 20, 6, 47, 21], - [7, 42, 14, 4, 43, 15], - - # 13 - [4, 133, 107], - [8, 59, 37, 1, 60, 38], - [8, 44, 20, 4, 45, 21], - [12, 33, 11, 4, 34, 12], - - # 14 - [3, 145, 115, 1, 146, 116], - [4, 64, 40, 5, 65, 41], - [11, 36, 16, 5, 37, 17], - [11, 36, 12, 5, 37, 13], - - # 15 - [5, 109, 87, 1, 110, 88], - [5, 65, 41, 5, 66, 42], - [5, 54, 24, 7, 55, 25], - [11, 36, 12], - - # 16 - [5, 122, 98, 1, 123, 99], - [7, 73, 45, 3, 74, 46], - [15, 43, 19, 2, 44, 20], - [3, 45, 15, 13, 46, 16], - - # 17 - [1, 135, 107, 5, 136, 108], - [10, 74, 46, 1, 75, 47], - [1, 50, 22, 15, 51, 23], - [2, 42, 14, 17, 43, 15], - - # 18 - [5, 150, 120, 1, 151, 121], - [9, 69, 43, 4, 70, 44], - [17, 50, 22, 1, 51, 23], - [2, 42, 14, 19, 43, 15], - - # 19 - [3, 141, 113, 4, 142, 114], - [3, 70, 44, 11, 71, 45], - [17, 47, 21, 4, 48, 22], - [9, 39, 13, 16, 40, 14], - - # 20 - [3, 135, 107, 5, 136, 108], - [3, 67, 41, 13, 68, 42], - [15, 54, 24, 5, 55, 25], - [15, 43, 15, 10, 44, 16], - - # 21 - [4, 144, 116, 4, 145, 117], - [17, 68, 42], - [17, 50, 22, 6, 51, 23], - [19, 46, 16, 6, 47, 17], - - # 22 - [2, 139, 111, 7, 140, 112], - [17, 74, 46], - [7, 54, 24, 16, 55, 25], - [34, 37, 13], - - # 23 - [4, 151, 121, 5, 152, 122], - [4, 75, 47, 14, 76, 48], - [11, 54, 24, 14, 55, 25], - [16, 45, 15, 14, 46, 16], - - # 24 - [6, 147, 117, 4, 148, 118], - [6, 73, 45, 14, 74, 46], - [11, 54, 24, 16, 55, 25], - [30, 46, 16, 2, 47, 17], - - # 25 - [8, 132, 106, 4, 133, 107], - [8, 75, 47, 13, 76, 48], - [7, 54, 24, 22, 55, 25], - [22, 45, 15, 13, 46, 16], - - # 26 - [10, 142, 114, 2, 143, 115], - [19, 74, 46, 4, 75, 47], - [28, 50, 22, 6, 51, 23], - [33, 46, 16, 4, 47, 17], - - # 27 - [8, 152, 122, 4, 153, 123], - [22, 73, 45, 3, 74, 46], - [8, 53, 23, 26, 54, 24], - [12, 45, 15, 28, 46, 16], - - # 28 - [3, 147, 117, 10, 148, 118], - [3, 73, 45, 23, 74, 46], - [4, 54, 24, 31, 55, 25], - [11, 45, 15, 31, 46, 16], - - # 29 - [7, 146, 116, 7, 147, 117], - [21, 73, 45, 7, 74, 46], - [1, 53, 23, 37, 54, 24], - [19, 45, 15, 26, 46, 16], - - # 30 - [5, 145, 115, 10, 146, 116], - [19, 75, 47, 10, 76, 48], - [15, 54, 24, 25, 55, 25], - [23, 45, 15, 25, 46, 16], - - # 31 - [13, 145, 115, 3, 146, 116], - [2, 74, 46, 29, 75, 47], - [42, 54, 24, 1, 55, 25], - [23, 45, 15, 28, 46, 16], - - # 32 - [17, 145, 115], - [10, 74, 46, 23, 75, 47], - [10, 54, 24, 35, 55, 25], - [19, 45, 15, 35, 46, 16], - - # 33 - [17, 145, 115, 1, 146, 116], - [14, 74, 46, 21, 75, 47], - [29, 54, 24, 19, 55, 25], - [11, 45, 15, 46, 46, 16], - - # 34 - [13, 145, 115, 6, 146, 116], - [14, 74, 46, 23, 75, 47], - [44, 54, 24, 7, 55, 25], - [59, 46, 16, 1, 47, 17], - - # 35 - [12, 151, 121, 7, 152, 122], - [12, 75, 47, 26, 76, 48], - [39, 54, 24, 14, 55, 25], - [22, 45, 15, 41, 46, 16], - - # 36 - [6, 151, 121, 14, 152, 122], - [6, 75, 47, 34, 76, 48], - [46, 54, 24, 10, 55, 25], - [2, 45, 15, 64, 46, 16], - - # 37 - [17, 152, 122, 4, 153, 123], - [29, 74, 46, 14, 75, 47], - [49, 54, 24, 10, 55, 25], - [24, 45, 15, 46, 46, 16], - - # 38 - [4, 152, 122, 18, 153, 123], - [13, 74, 46, 32, 75, 47], - [48, 54, 24, 14, 55, 25], - [42, 45, 15, 32, 46, 16], - - # 39 - [20, 147, 117, 4, 148, 118], - [40, 75, 47, 7, 76, 48], - [43, 54, 24, 22, 55, 25], - [10, 45, 15, 67, 46, 16], - - # 40 - [19, 148, 118, 6, 149, 119], - [18, 75, 47, 31, 76, 48], - [34, 54, 24, 34, 55, 25], - [20, 45, 15, 61, 46, 16] - - ] - - def __init__(self, totalCount, dataCount): - self.totalCount = totalCount - self.dataCount = dataCount - - @staticmethod - def getRSBlocks(typeNumber, errorCorrectLevel): - rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); - if rsBlock == None: - raise Exception("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel) - - length = len(rsBlock) / 3 - - list = [] - - for i in range(length): - - count = rsBlock[i * 3 + 0] - totalCount = rsBlock[i * 3 + 1] - dataCount = rsBlock[i * 3 + 2] - - for j in range(count): - list.append(QRRSBlock(totalCount, dataCount)) - - return list; - - @staticmethod - def getRsBlockTable(typeNumber, errorCorrectLevel): - if errorCorrectLevel == QRErrorCorrectLevel.L: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; - elif errorCorrectLevel == QRErrorCorrectLevel.M: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; - elif errorCorrectLevel == QRErrorCorrectLevel.Q: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; - elif errorCorrectLevel == QRErrorCorrectLevel.H: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; - else: - return None; - -class QRBitBuffer: - def __init__(self): - self.buffer = [] - self.length = 0 - def __repr__(self): - return ".".join([str(n) for n in self.buffer]) - def get(self, index): - bufIndex = math.floor(index / 8) - val = ( (self.buffer[bufIndex] >> (7 - index % 8) ) & 1) == 1 - print "get ", val - return ( (self.buffer[bufIndex] >> (7 - index % 8) ) & 1) == 1 - def put(self, num, length): - for i in range(length): - self.putBit( ( (num >> (length - i - 1) ) & 1) == 1) - def getLengthInBits(self): - return self.length - def putBit(self, bit): - bufIndex = self.length // 8 - if len(self.buffer) <= bufIndex: - self.buffer.append(0) - if bit: - self.buffer[bufIndex] |= (0x80 >> (self.length % 8) ) - self.length+=1 diff --git a/ripemd.py b/ripemd.py deleted file mode 100644 index a9d652c..0000000 --- a/ripemd.py +++ /dev/null @@ -1,399 +0,0 @@ -## ripemd.py - pure Python implementation of the RIPEMD-160 algorithm. -## Bjorn Edstrom 16 december 2007. -## -## Copyrights -## ========== -## -## This code is a derived from an implementation by Markus Friedl which is -## subject to the following license. This Python implementation is not -## subject to any other license. -## -##/* -## * Copyright (c) 2001 Markus Friedl. All rights reserved. -## * -## * Redistribution and use in source and binary forms, with or without -## * modification, are permitted provided that the following conditions -## * are met: -## * 1. Redistributions of source code must retain the above copyright -## * notice, this list of conditions and the following disclaimer. -## * 2. Redistributions in binary form must reproduce the above copyright -## * notice, this list of conditions and the following disclaimer in the -## * documentation and/or other materials provided with the distribution. -## * -## * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -## * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -## * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -## * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -## * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -## * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -## * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -## */ -##/* -## * Preneel, Bosselaers, Dobbertin, "The Cryptographic Hash Function RIPEMD-160", -## * RSA Laboratories, CryptoBytes, Volume 3, Number 2, Autumn 1997, -## * ftp://ftp.rsasecurity.com/pub/cryptobytes/crypto3n2.pdf -## */ - -try: - import psyco - psyco.full() -except ImportError: - pass - -#block_size = 1 -digest_size = 20 -digestsize = 20 - -class RIPEMD160: - """Return a new RIPEMD160 object. An optional string argument - may be provided; if present, this string will be automatically - hashed.""" - - def __init__(self, arg=None): - self.ctx = RMDContext() - if arg: - self.update(arg) - self.dig = None - - def update(self, arg): - """update(arg)""" - RMD160Update(self.ctx, arg, len(arg)) - self.dig = None - - def digest(self): - """digest()""" - if self.dig: - return self.dig - ctx = self.ctx.copy() - self.dig = RMD160Final(self.ctx) - self.ctx = ctx - return self.dig - - def hexdigest(self): - """hexdigest()""" - dig = self.digest() - hex_digest = '' - for d in dig: - hex_digest += '%02x' % ord(d) - return hex_digest - - def copy(self): - """copy()""" - import copy - return copy.deepcopy(self) - - - -def new(arg=None): - """Return a new RIPEMD160 object. An optional string argument - may be provided; if present, this string will be automatically - hashed.""" - return RIPEMD160(arg) - - - -# -# Private. -# - -class RMDContext: - def __init__(self): - self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE, - 0x10325476, 0xC3D2E1F0] # uint32 - self.count = 0 # uint64 - self.buffer = [0]*64 # uchar - def copy(self): - ctx = RMDContext() - ctx.state = self.state[:] - ctx.count = self.count - ctx.buffer = self.buffer[:] - return ctx - -K0 = 0x00000000 -K1 = 0x5A827999 -K2 = 0x6ED9EBA1 -K3 = 0x8F1BBCDC -K4 = 0xA953FD4E - -KK0 = 0x50A28BE6 -KK1 = 0x5C4DD124 -KK2 = 0x6D703EF3 -KK3 = 0x7A6D76E9 -KK4 = 0x00000000 - -def ROL(n, x): - return ((x << n) & 0xffffffff) | (x >> (32 - n)) - -def F0(x, y, z): - return x ^ y ^ z - -def F1(x, y, z): - return (x & y) | (((~x) % 0x100000000) & z) - -def F2(x, y, z): - return (x | ((~y) % 0x100000000)) ^ z - -def F3(x, y, z): - return (x & z) | (((~z) % 0x100000000) & y) - -def F4(x, y, z): - return x ^ (y | ((~z) % 0x100000000)) - -def R(a, b, c, d, e, Fj, Kj, sj, rj, X): - a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e - c = ROL(10, c) - return a % 0x100000000, c - -PADDING = [0x80] + [0]*63 - -import sys -import struct - -def RMD160Transform(state, block): #uint32 state[5], uchar block[64] - x = [0]*16 - if sys.byteorder == 'little': - x = struct.unpack('<16L', ''.join([chr(x) for x in block[0:64]])) - else: - raise "Error!!" - a = state[0] - b = state[1] - c = state[2] - d = state[3] - e = state[4] - - #/* Round 1 */ - a, c = R(a, b, c, d, e, F0, K0, 11, 0, x); - e, b = R(e, a, b, c, d, F0, K0, 14, 1, x); - d, a = R(d, e, a, b, c, F0, K0, 15, 2, x); - c, e = R(c, d, e, a, b, F0, K0, 12, 3, x); - b, d = R(b, c, d, e, a, F0, K0, 5, 4, x); - a, c = R(a, b, c, d, e, F0, K0, 8, 5, x); - e, b = R(e, a, b, c, d, F0, K0, 7, 6, x); - d, a = R(d, e, a, b, c, F0, K0, 9, 7, x); - c, e = R(c, d, e, a, b, F0, K0, 11, 8, x); - b, d = R(b, c, d, e, a, F0, K0, 13, 9, x); - a, c = R(a, b, c, d, e, F0, K0, 14, 10, x); - e, b = R(e, a, b, c, d, F0, K0, 15, 11, x); - d, a = R(d, e, a, b, c, F0, K0, 6, 12, x); - c, e = R(c, d, e, a, b, F0, K0, 7, 13, x); - b, d = R(b, c, d, e, a, F0, K0, 9, 14, x); - a, c = R(a, b, c, d, e, F0, K0, 8, 15, x); #/* #15 */ - #/* Round 2 */ - e, b = R(e, a, b, c, d, F1, K1, 7, 7, x); - d, a = R(d, e, a, b, c, F1, K1, 6, 4, x); - c, e = R(c, d, e, a, b, F1, K1, 8, 13, x); - b, d = R(b, c, d, e, a, F1, K1, 13, 1, x); - a, c = R(a, b, c, d, e, F1, K1, 11, 10, x); - e, b = R(e, a, b, c, d, F1, K1, 9, 6, x); - d, a = R(d, e, a, b, c, F1, K1, 7, 15, x); - c, e = R(c, d, e, a, b, F1, K1, 15, 3, x); - b, d = R(b, c, d, e, a, F1, K1, 7, 12, x); - a, c = R(a, b, c, d, e, F1, K1, 12, 0, x); - e, b = R(e, a, b, c, d, F1, K1, 15, 9, x); - d, a = R(d, e, a, b, c, F1, K1, 9, 5, x); - c, e = R(c, d, e, a, b, F1, K1, 11, 2, x); - b, d = R(b, c, d, e, a, F1, K1, 7, 14, x); - a, c = R(a, b, c, d, e, F1, K1, 13, 11, x); - e, b = R(e, a, b, c, d, F1, K1, 12, 8, x); #/* #31 */ - #/* Round 3 */ - d, a = R(d, e, a, b, c, F2, K2, 11, 3, x); - c, e = R(c, d, e, a, b, F2, K2, 13, 10, x); - b, d = R(b, c, d, e, a, F2, K2, 6, 14, x); - a, c = R(a, b, c, d, e, F2, K2, 7, 4, x); - e, b = R(e, a, b, c, d, F2, K2, 14, 9, x); - d, a = R(d, e, a, b, c, F2, K2, 9, 15, x); - c, e = R(c, d, e, a, b, F2, K2, 13, 8, x); - b, d = R(b, c, d, e, a, F2, K2, 15, 1, x); - a, c = R(a, b, c, d, e, F2, K2, 14, 2, x); - e, b = R(e, a, b, c, d, F2, K2, 8, 7, x); - d, a = R(d, e, a, b, c, F2, K2, 13, 0, x); - c, e = R(c, d, e, a, b, F2, K2, 6, 6, x); - b, d = R(b, c, d, e, a, F2, K2, 5, 13, x); - a, c = R(a, b, c, d, e, F2, K2, 12, 11, x); - e, b = R(e, a, b, c, d, F2, K2, 7, 5, x); - d, a = R(d, e, a, b, c, F2, K2, 5, 12, x); #/* #47 */ - #/* Round 4 */ - c, e = R(c, d, e, a, b, F3, K3, 11, 1, x); - b, d = R(b, c, d, e, a, F3, K3, 12, 9, x); - a, c = R(a, b, c, d, e, F3, K3, 14, 11, x); - e, b = R(e, a, b, c, d, F3, K3, 15, 10, x); - d, a = R(d, e, a, b, c, F3, K3, 14, 0, x); - c, e = R(c, d, e, a, b, F3, K3, 15, 8, x); - b, d = R(b, c, d, e, a, F3, K3, 9, 12, x); - a, c = R(a, b, c, d, e, F3, K3, 8, 4, x); - e, b = R(e, a, b, c, d, F3, K3, 9, 13, x); - d, a = R(d, e, a, b, c, F3, K3, 14, 3, x); - c, e = R(c, d, e, a, b, F3, K3, 5, 7, x); - b, d = R(b, c, d, e, a, F3, K3, 6, 15, x); - a, c = R(a, b, c, d, e, F3, K3, 8, 14, x); - e, b = R(e, a, b, c, d, F3, K3, 6, 5, x); - d, a = R(d, e, a, b, c, F3, K3, 5, 6, x); - c, e = R(c, d, e, a, b, F3, K3, 12, 2, x); #/* #63 */ - #/* Round 5 */ - b, d = R(b, c, d, e, a, F4, K4, 9, 4, x); - a, c = R(a, b, c, d, e, F4, K4, 15, 0, x); - e, b = R(e, a, b, c, d, F4, K4, 5, 5, x); - d, a = R(d, e, a, b, c, F4, K4, 11, 9, x); - c, e = R(c, d, e, a, b, F4, K4, 6, 7, x); - b, d = R(b, c, d, e, a, F4, K4, 8, 12, x); - a, c = R(a, b, c, d, e, F4, K4, 13, 2, x); - e, b = R(e, a, b, c, d, F4, K4, 12, 10, x); - d, a = R(d, e, a, b, c, F4, K4, 5, 14, x); - c, e = R(c, d, e, a, b, F4, K4, 12, 1, x); - b, d = R(b, c, d, e, a, F4, K4, 13, 3, x); - a, c = R(a, b, c, d, e, F4, K4, 14, 8, x); - e, b = R(e, a, b, c, d, F4, K4, 11, 11, x); - d, a = R(d, e, a, b, c, F4, K4, 8, 6, x); - c, e = R(c, d, e, a, b, F4, K4, 5, 15, x); - b, d = R(b, c, d, e, a, F4, K4, 6, 13, x); #/* #79 */ - - aa = a; - bb = b; - cc = c; - dd = d; - ee = e; - - a = state[0] - b = state[1] - c = state[2] - d = state[3] - e = state[4] - - #/* Parallel round 1 */ - a, c = R(a, b, c, d, e, F4, KK0, 8, 5, x) - e, b = R(e, a, b, c, d, F4, KK0, 9, 14, x) - d, a = R(d, e, a, b, c, F4, KK0, 9, 7, x) - c, e = R(c, d, e, a, b, F4, KK0, 11, 0, x) - b, d = R(b, c, d, e, a, F4, KK0, 13, 9, x) - a, c = R(a, b, c, d, e, F4, KK0, 15, 2, x) - e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x) - d, a = R(d, e, a, b, c, F4, KK0, 5, 4, x) - c, e = R(c, d, e, a, b, F4, KK0, 7, 13, x) - b, d = R(b, c, d, e, a, F4, KK0, 7, 6, x) - a, c = R(a, b, c, d, e, F4, KK0, 8, 15, x) - e, b = R(e, a, b, c, d, F4, KK0, 11, 8, x) - d, a = R(d, e, a, b, c, F4, KK0, 14, 1, x) - c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x) - b, d = R(b, c, d, e, a, F4, KK0, 12, 3, x) - a, c = R(a, b, c, d, e, F4, KK0, 6, 12, x) #/* #15 */ - #/* Parallel round 2 */ - e, b = R(e, a, b, c, d, F3, KK1, 9, 6, x) - d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x) - c, e = R(c, d, e, a, b, F3, KK1, 15, 3, x) - b, d = R(b, c, d, e, a, F3, KK1, 7, 7, x) - a, c = R(a, b, c, d, e, F3, KK1, 12, 0, x) - e, b = R(e, a, b, c, d, F3, KK1, 8, 13, x) - d, a = R(d, e, a, b, c, F3, KK1, 9, 5, x) - c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x) - b, d = R(b, c, d, e, a, F3, KK1, 7, 14, x) - a, c = R(a, b, c, d, e, F3, KK1, 7, 15, x) - e, b = R(e, a, b, c, d, F3, KK1, 12, 8, x) - d, a = R(d, e, a, b, c, F3, KK1, 7, 12, x) - c, e = R(c, d, e, a, b, F3, KK1, 6, 4, x) - b, d = R(b, c, d, e, a, F3, KK1, 15, 9, x) - a, c = R(a, b, c, d, e, F3, KK1, 13, 1, x) - e, b = R(e, a, b, c, d, F3, KK1, 11, 2, x) #/* #31 */ - #/* Parallel round 3 */ - d, a = R(d, e, a, b, c, F2, KK2, 9, 15, x) - c, e = R(c, d, e, a, b, F2, KK2, 7, 5, x) - b, d = R(b, c, d, e, a, F2, KK2, 15, 1, x) - a, c = R(a, b, c, d, e, F2, KK2, 11, 3, x) - e, b = R(e, a, b, c, d, F2, KK2, 8, 7, x) - d, a = R(d, e, a, b, c, F2, KK2, 6, 14, x) - c, e = R(c, d, e, a, b, F2, KK2, 6, 6, x) - b, d = R(b, c, d, e, a, F2, KK2, 14, 9, x) - a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x) - e, b = R(e, a, b, c, d, F2, KK2, 13, 8, x) - d, a = R(d, e, a, b, c, F2, KK2, 5, 12, x) - c, e = R(c, d, e, a, b, F2, KK2, 14, 2, x) - b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x) - a, c = R(a, b, c, d, e, F2, KK2, 13, 0, x) - e, b = R(e, a, b, c, d, F2, KK2, 7, 4, x) - d, a = R(d, e, a, b, c, F2, KK2, 5, 13, x) #/* #47 */ - #/* Parallel round 4 */ - c, e = R(c, d, e, a, b, F1, KK3, 15, 8, x) - b, d = R(b, c, d, e, a, F1, KK3, 5, 6, x) - a, c = R(a, b, c, d, e, F1, KK3, 8, 4, x) - e, b = R(e, a, b, c, d, F1, KK3, 11, 1, x) - d, a = R(d, e, a, b, c, F1, KK3, 14, 3, x) - c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x) - b, d = R(b, c, d, e, a, F1, KK3, 6, 15, x) - a, c = R(a, b, c, d, e, F1, KK3, 14, 0, x) - e, b = R(e, a, b, c, d, F1, KK3, 6, 5, x) - d, a = R(d, e, a, b, c, F1, KK3, 9, 12, x) - c, e = R(c, d, e, a, b, F1, KK3, 12, 2, x) - b, d = R(b, c, d, e, a, F1, KK3, 9, 13, x) - a, c = R(a, b, c, d, e, F1, KK3, 12, 9, x) - e, b = R(e, a, b, c, d, F1, KK3, 5, 7, x) - d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x) - c, e = R(c, d, e, a, b, F1, KK3, 8, 14, x) #/* #63 */ - #/* Parallel round 5 */ - b, d = R(b, c, d, e, a, F0, KK4, 8, 12, x) - a, c = R(a, b, c, d, e, F0, KK4, 5, 15, x) - e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x) - d, a = R(d, e, a, b, c, F0, KK4, 9, 4, x) - c, e = R(c, d, e, a, b, F0, KK4, 12, 1, x) - b, d = R(b, c, d, e, a, F0, KK4, 5, 5, x) - a, c = R(a, b, c, d, e, F0, KK4, 14, 8, x) - e, b = R(e, a, b, c, d, F0, KK4, 6, 7, x) - d, a = R(d, e, a, b, c, F0, KK4, 8, 6, x) - c, e = R(c, d, e, a, b, F0, KK4, 13, 2, x) - b, d = R(b, c, d, e, a, F0, KK4, 6, 13, x) - a, c = R(a, b, c, d, e, F0, KK4, 5, 14, x) - e, b = R(e, a, b, c, d, F0, KK4, 15, 0, x) - d, a = R(d, e, a, b, c, F0, KK4, 13, 3, x) - c, e = R(c, d, e, a, b, F0, KK4, 11, 9, x) - b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) #/* #79 */ - - t = (state[1] + cc + d) % 0x100000000; - state[1] = (state[2] + dd + e) % 0x100000000; - state[2] = (state[3] + ee + a) % 0x100000000; - state[3] = (state[4] + aa + b) % 0x100000000; - state[4] = (state[0] + bb + c) % 0x100000000; - state[0] = t % 0x100000000; - - pass - - -def RMD160Update(ctx, inp, inplen): - if type(inp) == str: - inp = [ord(i)&0xff for i in inp] - - have = (ctx.count / 8) % 64 - need = 64 - have - ctx.count += 8 * inplen - off = 0 - if inplen >= need: - if have: - for i in xrange(need): - ctx.buffer[have+i] = inp[i] - RMD160Transform(ctx.state, ctx.buffer) - off = need - have = 0 - while off + 64 <= inplen: - RMD160Transform(ctx.state, inp[off:]) #<--- - off += 64 - if off < inplen: - # memcpy(ctx->buffer + have, input+off, len-off); - for i in xrange(inplen - off): - ctx.buffer[have+i] = inp[off+i] - -def RMD160Final(ctx): - size = struct.pack(". - - -import sys, base64, os, re, hashlib, copy, operator, ast, threading, random - -try: - import ecdsa - from ecdsa.util import string_to_number, number_to_string -except: - print "python-ecdsa does not seem to be installed. Try 'sudo easy_install ecdsa'" - sys.exit(1) - -try: - import aes -except: - print "AES does not seem to be installed. Try 'sudo easy_install slowaes'" - sys.exit(1) - - -############ functions from pywallet ##################### - -addrtype = 0 - -def hash_160(public_key): - try: - md = hashlib.new('ripemd160') - md.update(hashlib.sha256(public_key).digest()) - return md.digest() - except: - import ripemd - md = ripemd.new(hashlib.sha256(public_key).digest()) - return md.digest() - - -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) - -def hash_160_to_bc_address(h160): - vh160 = chr(addrtype) + h160 - h = Hash(vh160) - addr = vh160 + h[0:4] - return b58encode(addr) - -def bc_address_to_hash_160(addr): - bytes = b58decode(addr, 25) - return bytes[1:21] - -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -__b58base = len(__b58chars) - -def b58encode(v): - """ encode v, which is a string of bytes, to base58. - """ - - long_value = 0L - for (i, c) in enumerate(v[::-1]): - long_value += (256**i) * ord(c) - - result = '' - while long_value >= __b58base: - div, mod = divmod(long_value, __b58base) - result = __b58chars[mod] + result - long_value = div - result = __b58chars[long_value] + result - - # Bitcoin does a little leading-zero-compression: - # leading 0-bytes in the input become leading-1s - nPad = 0 - for c in v: - if c == '\0': nPad += 1 - else: break - - return (__b58chars[0]*nPad) + result - -def b58decode(v, length): - """ decode v into a string of len bytes - """ - long_value = 0L - for (i, c) in enumerate(v[::-1]): - long_value += __b58chars.find(c) * (__b58base**i) - - result = '' - while long_value >= 256: - div, mod = divmod(long_value, 256) - result = chr(mod) + result - long_value = div - result = chr(long_value) + result - - nPad = 0 - for c in v: - if c == __b58chars[0]: nPad += 1 - else: break - - result = chr(0)*nPad + result - if length is not None and len(result) != length: - return None - - return result - - -def Hash(data): - return hashlib.sha256(hashlib.sha256(data).digest()).digest() - -def EncodeBase58Check(vchIn): - hash = Hash(vchIn) - return b58encode(vchIn + hash[0:4]) - -def DecodeBase58Check(psz): - vchRet = b58decode(psz, None) - key = vchRet[0:-4] - csum = vchRet[-4:] - hash = Hash(key) - cs32 = hash[0:4] - if cs32 != csum: - return None - else: - return key - -def PrivKeyToSecret(privkey): - return privkey[9:9+32] - -def SecretToASecret(secret): - vchIn = chr(addrtype+128) + secret - return EncodeBase58Check(vchIn) - -def ASecretToSecret(key): - vch = DecodeBase58Check(key) - if vch and vch[0] == chr(addrtype+128): - return vch[1:] - else: - return False - -########### end pywallet functions ####################### - -# URL decode -_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE) -urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x) - - -def int_to_hex(i, length=1): - s = hex(i)[2:].rstrip('L') - s = "0"*(2*length - len(s)) + s - return s.decode('hex')[::-1].encode('hex') - - -# AES -EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) -DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) - - - -# secp256k1, http://www.oid-info.com/get/1.3.132.0.10 -_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL -_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L -_b = 0x0000000000000000000000000000000000000000000000000000000000000007L -_a = 0x0000000000000000000000000000000000000000000000000000000000000000L -_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L -_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L -curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) -generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) -oid_secp256k1 = (1,3,132,0,10) -SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) - - -def filter(s): - out = re.sub('( [^\n]*|)\n','',s) - out = out.replace(' ','') - out = out.replace('\n','') - return out - -def raw_tx( inputs, outputs, for_sig = None ): - s = int_to_hex(1,4) + ' version\n' - s += int_to_hex( len(inputs) ) + ' number of inputs\n' - for i in range(len(inputs)): - _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i] - s += p_hash.decode('hex')[::-1].encode('hex') + ' prev hash\n' - s += int_to_hex(p_index,4) + ' prev index\n' - if for_sig is None: - sig = sig + chr(1) # hashtype - script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig) - script += sig.encode('hex') + ' sig\n' - pubkey = chr(4) + pubkey - script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey) - script += pubkey.encode('hex') + ' pubkey\n' - elif for_sig==i: - script = p_script + ' scriptsig \n' - else: - script='' - s += int_to_hex( len(filter(script))/2 ) + ' script length \n' - s += script - s += "ffffffff" + ' sequence\n' - s += int_to_hex( len(outputs) ) + ' number of outputs\n' - for output in outputs: - addr, amount = output - s += int_to_hex( amount, 8) + ' amount: %d\n'%amount - script = '76a9' # op_dup, op_hash_160 - script += '14' # push 0x14 bytes - script += bc_address_to_hash_160(addr).encode('hex') - script += '88ac' # op_equalverify, op_checksig - s += int_to_hex( len(filter(script))/2 ) + ' script length \n' - s += script + ' script \n' - s += int_to_hex(0,4) # lock time - if for_sig is not None: s += int_to_hex(1, 4) # hash type - return s - - - - -def format_satoshis(x, is_diff=False, num_zeros = 0): - from decimal import Decimal - s = str( Decimal(x) /100000000 ) - if is_diff and x>0: - s = "+" + s - if not '.' in s: s += '.' - p = s.find('.') - s += "0"*( 1 + num_zeros - ( len(s) - p )) - s += " "*( 9 - ( len(s) - p )) - s = " "*( 5 - ( p )) + s - return s - - -from version import ELECTRUM_VERSION, SEED_VERSION -from interface import DEFAULT_SERVERS - - - - -class Wallet: - def __init__(self, gui_callback = lambda: None): - - self.electrum_version = ELECTRUM_VERSION - self.seed_version = SEED_VERSION - self.gui_callback = gui_callback - - self.gap_limit = 5 # configuration - self.fee = 100000 - self.num_zeros = 0 - self.master_public_key = '' - - # saved fields - self.use_encryption = False - self.addresses = [] # receiving addresses visible for user - self.change_addresses = [] # addresses used as change - self.seed = '' # encrypted - self.history = {} - self.labels = {} # labels for addresses and transactions - self.aliases = {} # aliases for addresses - self.authorities = {} # trusted addresses - self.frozen_addresses = [] - - self.receipts = {} # signed URIs - self.receipt = None # next receipt - self.addressbook = [] # outgoing addresses, for payments - - # not saved - self.tx_history = {} - - self.imported_keys = {} - self.remote_url = None - - self.was_updated = True - self.blocks = -1 - self.banner = '' - - # there is a difference between self.up_to_date and self.is_up_to_date() - # self.is_up_to_date() returns true when all requests have been answered and processed - # self.up_to_date is true when the wallet is synchronized (stronger requirement) - self.up_to_date_event = threading.Event() - self.up_to_date_event.clear() - self.up_to_date = False - self.lock = threading.Lock() - self.tx_event = threading.Event() - - self.pick_random_server() - - - - def pick_random_server(self): - self.server = random.choice( DEFAULT_SERVERS ) # random choice when the wallet is created - - def is_up_to_date(self): - return self.interface.responses.empty() and not self.interface.unanswered_requests - - def set_server(self, server): - # raise an error if the format isnt correct - a,b,c = server.split(':') - b = int(b) - assert c in ['t','h','n'] - # set the server - if server != self.server: - self.server = server - self.save() - self.interface.is_connected = False # this exits the polling loop - - def set_path(self, wallet_path): - - if wallet_path is not None: - self.path = wallet_path - else: - # backward compatibility: look for wallet file in the default data directory - if "HOME" in os.environ: - wallet_dir = os.path.join( os.environ["HOME"], '.electrum') - elif "LOCALAPPDATA" in os.environ: - wallet_dir = os.path.join( os.environ["LOCALAPPDATA"], 'Electrum' ) - elif "APPDATA" in os.environ: - wallet_dir = os.path.join( os.environ["APPDATA"], 'Electrum' ) - else: - raise BaseException("No home directory found in environment variables.") - - if not os.path.exists( wallet_dir ): os.mkdir( wallet_dir ) - self.path = os.path.join( wallet_dir, 'electrum.dat' ) - - def import_key(self, keypair, password): - address, key = keypair.split(':') - if not self.is_valid(address): return False - if address in self.all_addresses(): return False - b = ASecretToSecret( key ) - if not b: return False - secexp = int( b.encode('hex'), 16) - private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 ) - # sanity check - public_key = private_key.get_verifying_key() - if not address == public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ): return False - self.imported_keys[address] = self.pw_encode( key, password ) - return True - - def new_seed(self, password): - seed = "%032x"%ecdsa.util.randrange( pow(2,128) ) - #self.init_mpk(seed) - # encrypt - self.seed = self.pw_encode( seed, password ) - - - def init_mpk(self,seed): - # public key - curve = SECP256k1 - secexp = self.stretch_key(seed) - master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) - self.master_public_key = master_private_key.get_verifying_key().to_string() - - def all_addresses(self): - return self.addresses + self.change_addresses + self.imported_keys.keys() - - def is_mine(self, address): - return address in self.all_addresses() - - def is_change(self, address): - return address in self.change_addresses - - def is_valid(self,addr): - ADDRESS_RE = re.compile('[1-9A-HJ-NP-Za-km-z]{26,}\\Z') - if not ADDRESS_RE.match(addr): return False - try: - h = bc_address_to_hash_160(addr) - except: - return False - return addr == hash_160_to_bc_address(h) - - def stretch_key(self,seed): - oldseed = seed - for i in range(100000): - seed = hashlib.sha256(seed + oldseed).digest() - return string_to_number( seed ) - - def get_sequence(self,n,for_change): - return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) ) - - def get_private_key(self, address, password): - """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ - order = generator_secp256k1.order() - - if address in self.imported_keys.keys(): - b = self.pw_decode( self.imported_keys[address], password ) - b = ASecretToSecret( b ) - secexp = int( b.encode('hex'), 16) - else: - if address in self.addresses: - n = self.addresses.index(address) - for_change = False - elif address in self.change_addresses: - n = self.change_addresses.index(address) - for_change = True - else: - raise BaseException("unknown address") - try: - seed = self.pw_decode( self.seed, password) - except: - raise BaseException("Invalid password") - secexp = self.stretch_key(seed) - secexp = ( secexp + self.get_sequence(n,for_change) ) % order - - pk = number_to_string(secexp,order) - return pk - - def msg_magic(self, message): - return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message - - def sign_message(self, address, message, password): - private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 ) - public_key = private_key.get_verifying_key() - signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string ) - assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string) - for i in range(4): - sig = base64.b64encode( chr(27+i) + signature ) - try: - self.verify_message( address, sig, message) - return sig - except: - continue - else: - raise BaseException("error: cannot sign message") - - - def verify_message(self, address, signature, message): - """ See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """ - from ecdsa import numbertheory, ellipticcurve, util - import msqr - curve = curve_secp256k1 - G = generator_secp256k1 - order = G.order() - # extract r,s from signature - sig = base64.b64decode(signature) - if len(sig) != 65: raise BaseException("Wrong encoding") - r,s = util.sigdecode_string(sig[1:], order) - recid = ord(sig[0]) - 27 - # 1.1 - x = r + (recid/2) * order - # 1.3 - alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p() - beta = msqr.modular_sqrt(alpha, curve.p()) - y = beta if (beta - recid) % 2 == 0 else curve.p() - beta - # 1.4 the constructor checks that nR is at infinity - R = ellipticcurve.Point(curve, x, y, order) - # 1.5 compute e from message: - h = Hash( self.msg_magic( message ) ) - e = string_to_number(h) - minus_e = -e % order - # 1.6 compute Q = r^-1 (sR - eG) - inv_r = numbertheory.inverse_mod(r,order) - Q = inv_r * ( s * R + minus_e * G ) - public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 ) - # check that Q is the public key - public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string) - # check that we get the original signing address - addr = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ) - # print addr - if address != addr: - print "bad signature" - raise BaseException("Bad signature") - - - def create_new_address(self, for_change): - """ Publickey(type,n) = Master_public_key + H(n|S|type)*point """ - curve = SECP256k1 - n = len(self.change_addresses) if for_change else len(self.addresses) - z = self.get_sequence(n,for_change) - master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key, curve = SECP256k1 ) - pubkey_point = master_public_key.pubkey.point + z*curve.generator - public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 ) - address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() ) - if for_change: - self.change_addresses.append(address) - else: - self.addresses.append(address) - - self.history[address] = [] - print address - return address - - - - def synchronize(self): - if not self.master_public_key: - return [] - - new_addresses = [] - while True: - if self.change_addresses == []: - new_addresses.append( self.create_new_address(True) ) - continue - a = self.change_addresses[-1] - if self.history.get(a): - new_addresses.append( self.create_new_address(True) ) - else: - break - - n = self.gap_limit - while True: - if len(self.addresses) < n: - new_addresses.append( self.create_new_address(False) ) - continue - if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]: - break - else: - new_addresses.append( self.create_new_address(False) ) - - if self.remote_url: - num = self.get_remote_number() - while len(self.addresses) 1 ) or ( len(self.addresses) > self.gap_limit ) - - def fill_addressbook(self): - for tx in self.tx_history.values(): - if tx['value']<0: - for i in tx['outputs']: - if not self.is_mine(i) and i not in self.addressbook: - self.addressbook.append(i) - # redo labels - self.update_tx_labels() - - - def save(self): - s = { - 'seed_version':self.seed_version, - 'use_encryption':self.use_encryption, - 'master_public_key': self.master_public_key.encode('hex'), - 'fee':self.fee, - 'server':self.server, - 'seed':self.seed, - 'addresses':self.addresses, - 'change_addresses':self.change_addresses, - 'history':self.history, - 'labels':self.labels, - 'contacts':self.addressbook, - 'imported_keys':self.imported_keys, - 'aliases':self.aliases, - 'authorities':self.authorities, - 'receipts':self.receipts, - 'num_zeros':self.num_zeros, - 'frozen_addresses':self.frozen_addresses, - } - f = open(self.path,"w") - f.write( repr(s) ) - f.close() - - def read(self): - import interface - - upgrade_msg = """This wallet seed is deprecated. Please run upgrade.py for a diagnostic.""" - self.file_exists = False - try: - f = open(self.path,"r") - data = f.read() - f.close() - except: - return - data = interface.old_to_new(data) - try: - d = ast.literal_eval( data ) - self.seed_version = d.get('seed_version') - self.master_public_key = d.get('master_public_key').decode('hex') - self.use_encryption = d.get('use_encryption') - self.fee = int( d.get('fee') ) - self.seed = d.get('seed') - self.server = d.get('server') - #blocks = d.get('blocks') - self.addresses = d.get('addresses') - self.change_addresses = d.get('change_addresses') - self.history = d.get('history') - self.labels = d.get('labels') - self.addressbook = d.get('contacts') - self.imported_keys = d.get('imported_keys',{}) - self.aliases = d.get('aliases',{}) - self.authorities = d.get('authorities',{}) - self.receipts = d.get('receipts',{}) - self.num_zeros = d.get('num_zeros',0) - self.frozen_addresses = d.get('frozen_addresses',[]) - except: - raise BaseException("cannot read wallet file") - - self.update_tx_history() - - if self.seed_version != SEED_VERSION: - raise BaseException(upgrade_msg) - - if self.remote_url: assert self.master_public_key.encode('hex') == self.get_remote_mpk() - - self.file_exists = True - - - - - def get_addr_balance(self, addr): - assert self.is_mine(addr) - h = self.history.get(addr,[]) - c = u = 0 - for item in h: - v = item['value'] - if item['height']: - c += v - else: - u += v - return c, u - - def get_balance(self): - conf = unconf = 0 - for addr in self.all_addresses(): - c, u = self.get_addr_balance(addr) - conf += c - unconf += u - return conf, unconf - - - def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ): - """ todo: minimize tx size """ - total = 0 - fee = self.fee if fixed_fee is None else fixed_fee - - coins = [] - domain = [from_addr] if from_addr else self.all_addresses() - for i in self.frozen_addresses: - if i in domain: domain.remove(i) - - for addr in domain: - h = self.history.get(addr) - if h is None: continue - for item in h: - if item.get('raw_output_script'): - coins.append( (addr,item)) - - coins = sorted( coins, key = lambda x: x[1]['timestamp'] ) - inputs = [] - for c in coins: - addr, item = c - v = item.get('value') - total += v - inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) ) - fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee - if total >= amount + fee: break - else: - #print "not enough funds: %d %d"%(total, fee) - inputs = [] - return inputs, total, fee - - def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ): - outputs = [ (to_addr, amount) ] - change_amount = total - ( amount + fee ) - if change_amount != 0: - # normally, the update thread should ensure that the last change address is unused - if not change_addr: - change_addr = self.change_addresses[-1] - outputs.append( ( change_addr, change_amount) ) - return outputs - - def sign_inputs( self, inputs, outputs, password ): - s_inputs = [] - for i in range(len(inputs)): - addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i] - private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 ) - public_key = private_key.get_verifying_key() - pubkey = public_key.to_string() - tx = filter( raw_tx( inputs, outputs, for_sig = i ) ) - sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) - assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) - s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) ) - return s_inputs - - def pw_encode(self, s, password): - if password: - secret = Hash(password) - return EncodeAES(secret, s) - else: - return s - - def pw_decode(self, s, password): - if password is not None: - secret = Hash(password) - d = DecodeAES(secret, s) - if s == self.seed: - try: - d.decode('hex') - except: - raise BaseException("Invalid password") - return d - else: - return s - - def get_status(self, address): - h = self.history.get(address) - if not h: - status = None - else: - lastpoint = h[-1] - status = lastpoint['block_hash'] - if status == 'mempool': - status = status + ':%d'% len(h) - return status - - def receive_status_callback(self, addr, status): - with self.lock: - if self.get_status(addr) != status: - #print "updating status for", addr, status - self.interface.get_history(addr) - - def receive_history_callback(self, addr, data): - #print "updating history for", addr - with self.lock: - self.history[addr] = data - self.update_tx_history() - self.save() - - def get_tx_history(self): - lines = self.tx_history.values() - lines = sorted(lines, key=operator.itemgetter("timestamp")) - return lines - - def update_tx_history(self): - self.tx_history= {} - for addr in self.all_addresses(): - h = self.history.get(addr) - if h is None: continue - for tx in h: - tx_hash = tx['tx_hash'] - line = self.tx_history.get(tx_hash) - if not line: - self.tx_history[tx_hash] = copy.copy(tx) - line = self.tx_history.get(tx_hash) - else: - line['value'] += tx['value'] - if line['height'] == 0: - line['timestamp'] = 1e12 - self.update_tx_labels() - - def update_tx_labels(self): - for tx in self.tx_history.values(): - default_label = '' - if tx['value']<0: - for o_addr in tx['outputs']: - if not self.is_change(o_addr): - dest_label = self.labels.get(o_addr) - if dest_label: - default_label = 'to: ' + dest_label - else: - default_label = 'to: ' + o_addr - else: - for o_addr in tx['outputs']: - if self.is_mine(o_addr) and not self.is_change(o_addr): - dest_label = self.labels.get(o_addr) - if dest_label: - default_label = 'at: ' + dest_label - else: - default_label = 'at: ' + o_addr - tx['default_label'] = default_label - - def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None): - if not self.is_valid(to_address): - raise BaseException("Invalid address") - inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr ) - if not inputs: - raise BaseException("Not enough funds") - outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr ) - s_inputs = self.sign_inputs( inputs, outputs, password ) - - tx = filter( raw_tx( s_inputs, outputs ) ) - if to_address not in self.addressbook: - self.addressbook.append(to_address) - if label: - tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex') - self.labels[tx_hash] = label - - return tx - - def sendtx(self, tx): - tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex') - self.tx_event.clear() - self.interface.send([('blockchain.transaction.broadcast', [tx])]) - self.tx_event.wait() - out = self.tx_result - if out != tx_hash: - return False, "error: " + out - if self.receipt: - self.receipts[tx_hash] = self.receipt - self.receipt = None - return True, out - - - def read_alias(self, alias): - # this might not be the right place for this function. - import urllib - - m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias) - m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias) - if m1: - url = 'http://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) - elif m2: - url = 'http://' + alias + '/bitcoin.id' - else: - return '' - try: - lines = urllib.urlopen(url).readlines() - except: - return '' - - # line 0 - line = lines[0].strip().split(':') - if len(line) == 1: - auth_name = None - target = signing_addr = line[0] - else: - target, auth_name, signing_addr, signature = line - msg = "alias:%s:%s:%s"%(alias,target,auth_name) - print msg, signature - self.verify_message(signing_addr, signature, msg) - - # other lines are signed updates - for line in lines[1:]: - line = line.strip() - if not line: continue - line = line.split(':') - previous = target - print repr(line) - target, signature = line - self.verify_message(previous, signature, "alias:%s:%s"%(alias,target)) - - if not self.is_valid(target): - raise BaseException("Invalid bitcoin address") - - return target, signing_addr, auth_name - - def update_password(self, seed, new_password): - if new_password == '': new_password = None - self.use_encryption = (new_password != None) - self.seed = self.pw_encode( seed, new_password) - for k in self.imported_keys.keys(): - a = self.imported_keys[k] - b = self.pw_decode(a, password) - c = self.pw_encode(b, new_password) - self.imported_keys[k] = c - self.save() - - def get_alias(self, alias, interactive = False, show_message=None, question = None): - try: - target, signing_address, auth_name = self.read_alias(alias) - except BaseException, e: - # raise exception if verify fails (verify the chain) - if interactive: - show_message("Alias error: " + e.message) - return - - print target, signing_address, auth_name - - if auth_name is None: - a = self.aliases.get(alias) - if not a: - msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address) - if interactive and question( msg ): - self.aliases[alias] = (signing_address, target) - else: - target = None - else: - if signing_address != a[0]: - msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias - if interactive and question( msg ): - self.aliases[alias] = (signing_address, target) - else: - target = None - else: - if signing_address not in self.authorities.keys(): - msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address) - if interactive and question( msg ): - self.authorities[signing_address] = auth_name - else: - target = None - - if target: - self.aliases[alias] = (signing_address, target) - - return target - - - def parse_url(self, url, show_message, question): - o = url[8:].split('?') - address = o[0] - if len(o)>1: - params = o[1].split('&') - else: - params = [] - - amount = label = message = signature = identity = '' - for p in params: - k,v = p.split('=') - uv = urldecode(v) - if k == 'amount': amount = uv - elif k == 'message': message = uv - elif k == 'label': label = uv - elif k == 'signature': - identity, signature = uv.split(':') - url = url.replace('&%s=%s'%(k,v),'') - else: - print k,v - - if signature: - if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity): - signing_address = self.get_alias(identity, True, show_message, question) - elif self.is_valid(identity): - signing_address = identity - else: - signing_address = None - if not signing_address: - return - try: - self.verify_message(signing_address, signature, url ) - self.receipt = (signing_address, signature, url) - except: - show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.') - address = amount = label = identity = message = '' - - if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address): - payto_address = self.get_alias(address, True, show_message, question) - if payto_address: - address = address + ' <' + payto_address + '>' - - return address, amount, label, message, signature, identity, url - - - def update(self): - self.interface.poke() - self.up_to_date_event.wait() - - - def start_session(self, interface): - self.interface = interface - self.interface.send([('server.banner',[]), ('blockchain.numblocks.subscribe',[]), ('server.peers.subscribe',[])]) - self.interface.subscribe(self.all_addresses()) - - - - diff --git a/watch_address b/watch_address index 162c5a6..41d2051 100755 --- a/watch_address +++ b/watch_address @@ -1,12 +1,14 @@ #!/usr/bin/env python -import interface, sys +import sys +from electrum import TcpStratumInterface + try: addr = sys.argv[1] except: print "usage: watch_address " -i = interface.TcpStratumInterface('ecdsa.org', 50001) +i = TcpStratumInterface('ecdsa.org', 50001) i.start() i.send([('blockchain.address.subscribe',[addr])]) -- 1.7.1