Merge branch 'master' into qt-switch-gui

This commit is contained in:
bkkcoins 2013-01-05 21:39:44 +07:00
commit 362057c738
7 changed files with 196 additions and 49 deletions

View File

@ -5,7 +5,6 @@ security:
wallet, transactions : wallet, transactions :
- support compressed keys
- dust sweeping - dust sweeping
- transactions with multiple outputs - transactions with multiple outputs
- BIP 32 - BIP 32

View File

@ -43,8 +43,57 @@ Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest()
hash_encode = lambda x: x[::-1].encode('hex') hash_encode = lambda x: x[::-1].encode('hex')
hash_decode = lambda x: x.decode('hex')[::-1] hash_decode = lambda x: x.decode('hex')[::-1]
############ functions from pywallet #####################
# pywallet openssl private key implementation
def i2d_ECPrivateKey(pkey, compressed=False):
if compressed:
key = '3081d30201010420' + \
'%064x' % pkey.secret + \
'a081a53081a2020101302c06072a8648ce3d0101022100' + \
'%064x' % _p + \
'3006040100040107042102' + \
'%064x' % _Gx + \
'022100' + \
'%064x' % _r + \
'020101a124032200'
else:
key = '308201130201010420' + \
'%064x' % pkey.secret + \
'a081a53081a2020101302c06072a8648ce3d0101022100' + \
'%064x' % _p + \
'3006040100040107044104' + \
'%064x' % _Gx + \
'%064x' % _Gy + \
'022100' + \
'%064x' % _r + \
'020101a144034200'
return key.decode('hex') + i2o_ECPublicKey(pkey, compressed)
def i2o_ECPublicKey(pkey, compressed=False):
# public keys are 65 bytes long (520 bits)
# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
if compressed:
if pkey.pubkey.point.y() & 1:
key = '03' + '%064x' % pkey.pubkey.point.x()
else:
key = '02' + '%064x' % pkey.pubkey.point.x()
else:
key = '04' + \
'%064x' % pkey.pubkey.point.x() + \
'%064x' % pkey.pubkey.point.y()
return key.decode('hex')
# end pywallet openssl private key implementation
############ functions from pywallet #####################
addrtype = 0 addrtype = 0
def hash_160(public_key): def hash_160(public_key):
@ -151,17 +200,39 @@ def DecodeBase58Check(psz):
def PrivKeyToSecret(privkey): def PrivKeyToSecret(privkey):
return privkey[9:9+32] return privkey[9:9+32]
def SecretToASecret(secret): def SecretToASecret(secret, compressed=False):
vchIn = chr(addrtype+128) + secret vchIn = chr((addrtype+128)&255) + secret
if compressed: vchIn += '\01'
return EncodeBase58Check(vchIn) return EncodeBase58Check(vchIn)
def ASecretToSecret(key): def ASecretToSecret(key):
vch = DecodeBase58Check(key) vch = DecodeBase58Check(key)
if vch and vch[0] == chr(addrtype+128): if vch and vch[0] == chr((addrtype+128)&255):
return vch[1:] return vch[1:]
else: else:
return False return False
def regenerate_key(sec):
b = ASecretToSecret(sec)
if not b:
return False
b = b[0:32]
secret = int('0x' + b.encode('hex'), 16)
return EC_KEY(secret)
def GetPubKey(pkey, compressed=False):
return i2o_ECPublicKey(pkey, compressed)
def GetPrivKey(pkey, compressed=False):
return i2d_ECPrivateKey(pkey, compressed)
def GetSecret(pkey):
return ('%064x' % pkey.secret).decode('hex')
def is_compressed(sec):
b = ASecretToSecret(sec)
return len(b) == 33
########### end pywallet functions ####################### ########### end pywallet functions #######################
# secp256k1, http://www.oid-info.com/get/1.3.132.0.10 # secp256k1, http://www.oid-info.com/get/1.3.132.0.10
@ -176,6 +247,13 @@ generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
oid_secp256k1 = (1,3,132,0,10) oid_secp256k1 = (1,3,132,0,10)
SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 )
class EC_KEY(object):
def __init__( self, secret ):
self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
self.secret = secret
def filter(s): def filter(s):
out = re.sub('( [^\n]*|)\n','',s) out = re.sub('( [^\n]*|)\n','',s)
@ -195,7 +273,6 @@ def raw_tx( inputs, outputs, for_sig = None ):
sig = sig + chr(1) # hashtype sig = sig + chr(1) # hashtype
script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig) script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig)
script += sig.encode('hex') + ' sig\n' script += sig.encode('hex') + ' sig\n'
pubkey = chr(4) + pubkey
script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey) script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey)
script += pubkey.encode('hex') + ' pubkey\n' script += pubkey.encode('hex') + ' pubkey\n'
elif for_sig==i: elif for_sig==i:

View File

@ -28,8 +28,11 @@ class Exchanger(threading.Thread):
self.discovery() self.discovery()
def discovery(self): def discovery(self):
connection = httplib.HTTPSConnection('blockchain.info') try:
connection.request("GET", "/ticker") connection = httplib.HTTPSConnection('blockchain.info')
connection.request("GET", "/ticker")
except:
return
response = connection.getresponse() response = connection.getresponse()
if response.reason == httplib.responses[httplib.NOT_FOUND]: if response.reason == httplib.responses[httplib.NOT_FOUND]:
return return
@ -43,9 +46,12 @@ class Exchanger(threading.Thread):
self.parent.emit(SIGNAL("refresh_balance()")) self.parent.emit(SIGNAL("refresh_balance()"))
except KeyError: except KeyError:
pass pass
def get_currencies(self):
return [] if self.quote_currencies == None else sorted(self.quote_currencies.keys())
def _lookup_rate(self, response, quote_id): def _lookup_rate(self, response, quote_id):
return decimal.Decimal(response[str(quote_id)]["15m"]) return decimal.Decimal(str(response[str(quote_id)]["15m"]))
if __name__ == "__main__": if __name__ == "__main__":
exch = Exchanger(("BRL", "CNY", "EUR", "GBP", "RUB", "USD")) exch = Exchanger(("BRL", "CNY", "EUR", "GBP", "RUB", "USD"))

View File

@ -38,6 +38,7 @@ except:
from wallet import format_satoshis from wallet import format_satoshis
import bmp, mnemonic, pyqrnative, qrscanner import bmp, mnemonic, pyqrnative, qrscanner
import exchange_rate
from decimal import Decimal from decimal import Decimal
@ -335,6 +336,9 @@ class ElectrumWindow(QMainWindow):
self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet) self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
#self.connect(self, SIGNAL('editamount'), self.edit_amount) #self.connect(self, SIGNAL('editamount'), self.edit_amount)
self.history_list.setFocus(True) self.history_list.setFocus(True)
self.exchanger = exchange_rate.Exchanger(self)
self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
# dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
if platform.system() == 'Windows': if platform.system() == 'Windows':
@ -384,6 +388,7 @@ class ElectrumWindow(QMainWindow):
c, u = self.wallet.get_balance() c, u = self.wallet.get_balance()
text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) ) 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() ) if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
text += self.create_quote_text(Decimal(c+u)/100000000)
icon = QIcon(":icons/status_connected.png") icon = QIcon(":icons/status_connected.png")
else: else:
text = _( "Not connected" ) text = _( "Not connected" )
@ -402,7 +407,15 @@ class ElectrumWindow(QMainWindow):
self.update_contacts_tab() self.update_contacts_tab()
self.update_completions() self.update_completions()
def create_quote_text(self, btc_balance):
quote_currency = self.config.get("currency", "None")
quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
if quote_balance is None:
quote_text = ""
else:
quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
return quote_text
def create_history_tab(self): def create_history_tab(self):
self.history_list = l = MyTreeWidget(self) self.history_list = l = MyTreeWidget(self)
l.setColumnCount(5) l.setColumnCount(5)
@ -1512,16 +1525,16 @@ class ElectrumWindow(QMainWindow):
tabs = QTabWidget(self) tabs = QTabWidget(self)
vbox.addWidget(tabs) vbox.addWidget(tabs)
tab = QWidget()
grid_wallet = QGridLayout(tab)
grid_wallet.setColumnStretch(0,1)
tabs.addTab(tab, _('Wallet') )
tab2 = QWidget() tab2 = QWidget()
grid_ui = QGridLayout(tab2) grid_ui = QGridLayout(tab2)
grid_ui.setColumnStretch(0,1) grid_ui.setColumnStretch(0,1)
tabs.addTab(tab2, _('Display') ) tabs.addTab(tab2, _('Display') )
tab = QWidget()
grid_wallet = QGridLayout(tab)
grid_wallet.setColumnStretch(0,1)
tabs.addTab(tab, _('Wallet') )
fee_label = QLabel(_('Transaction fee')) fee_label = QLabel(_('Transaction fee'))
grid_wallet.addWidget(fee_label, 2, 0) grid_wallet.addWidget(fee_label, 2, 0)
fee_e = QLineEdit() fee_e = QLineEdit()
@ -1575,23 +1588,19 @@ class ElectrumWindow(QMainWindow):
gui_label=QLabel(_('Default GUI') + ':') gui_label=QLabel(_('Default GUI') + ':')
grid_ui.addWidget(gui_label , 7, 0) grid_ui.addWidget(gui_label , 7, 0)
gui_combo = QComboBox() gui_combo = QComboBox()
gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text']) gui_combo.addItems(['Lite', 'Classic'])
index = gui_combo.findText(self.config.get("gui","classic").capitalize()) index = gui_combo.findText(self.config.get("gui","classic").capitalize())
if index==-1: index = 1 if index==-1: index = 1
gui_combo.setCurrentIndex(index) gui_combo.setCurrentIndex(index)
grid_ui.addWidget(gui_combo, 7, 1) grid_ui.addWidget(gui_combo, 7, 1)
grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2) grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up.'+'\n'+'Note: use the command line to access the "text" and "gtk" GUIs')), 7, 2)
if not self.config.is_modifiable('gui'): if not self.config.is_modifiable('gui'):
for w in [gui_combo, gui_label]: w.setEnabled(False) for w in [gui_combo, gui_label]: w.setEnabled(False)
lang_label=QLabel(_('Language') + ':') lang_label=QLabel(_('Language') + ':')
grid_ui.addWidget(lang_label , 8, 0) grid_ui.addWidget(lang_label , 8, 0)
lang_combo = QComboBox() lang_combo = QComboBox()
languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'), from i18n import languages
'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
}
lang_combo.addItems(languages.values()) lang_combo.addItems(languages.values())
try: try:
index = languages.keys().index(self.config.get("language",'')) index = languages.keys().index(self.config.get("language",''))
@ -1603,19 +1612,33 @@ class ElectrumWindow(QMainWindow):
if not self.config.is_modifiable('language'): if not self.config.is_modifiable('language'):
for w in [lang_combo, lang_label]: w.setEnabled(False) for w in [lang_combo, lang_label]: w.setEnabled(False)
currencies = self.exchanger.get_currencies()
currencies.insert(0, "None")
cur_label=QLabel(_('Currency') + ':')
grid_ui.addWidget(cur_label , 9, 0)
cur_combo = QComboBox()
cur_combo.addItems(currencies)
try:
index = currencies.index(self.config.get('currency', "None"))
except:
index = 0
cur_combo.setCurrentIndex(index)
grid_ui.addWidget(cur_combo, 9, 1)
grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes. ')), 9, 2)
view_label=QLabel(_('Receive Tab') + ':') view_label=QLabel(_('Receive Tab') + ':')
grid_ui.addWidget(view_label , 9, 0) grid_ui.addWidget(view_label , 10, 0)
view_combo = QComboBox() view_combo = QComboBox()
view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')]) view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
view_combo.setCurrentIndex(self.receive_tab_mode) view_combo.setCurrentIndex(self.receive_tab_mode)
grid_ui.addWidget(view_combo, 9, 1) grid_ui.addWidget(view_combo, 10, 1)
hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \ hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
+ _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \ + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
+ _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \ + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
+ _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n' + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
grid_ui.addWidget(HelpButton(hh), 9, 2) grid_ui.addWidget(HelpButton(hh), 10, 2)
vbox.addLayout(ok_cancel_buttons(d)) vbox.addLayout(ok_cancel_buttons(d))
d.setLayout(vbox) d.setLayout(vbox)
@ -1678,6 +1701,11 @@ class ElectrumWindow(QMainWindow):
if lang_request != self.config.get('language'): if lang_request != self.config.get('language'):
self.config.set_key("language", lang_request, True) self.config.set_key("language", lang_request, True)
need_restart = True need_restart = True
cur_request = str(currencies[cur_combo.currentIndex()])
if cur_request != self.config.get('currency', "None"):
self.config.set_key('currency', cur_request, True)
self.update_wallet()
if need_restart: if need_restart:
QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK')) QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))

View File

@ -34,3 +34,20 @@ def set_language(x):
if x: language = gettext.translation('electrum', LOCALE_DIR, fallback = True, languages=[x]) if x: language = gettext.translation('electrum', LOCALE_DIR, fallback = True, languages=[x])
languages = {
'':_('Default'),
'br':_('Brasilian'),
'cs':_('Czech'),
'de':_('German'),
'eo':_('Esperanto'),
'en':_('English'),
'es':_('Spanish'),
'fr':_('French'),
'it':_('Italian'),
'lv':_('Latvian'),
'nl':_('Dutch'),
'ru':_('Russian'),
'sl':_('Slovenian'),
'vi':_('Vietnamese'),
'zh':_('Chinese')
}

View File

@ -94,7 +94,7 @@ a SimpleConfig instance then reads the wallet file.
try: try:
out = ast.literal_eval(out) out = ast.literal_eval(out)
except: except:
print "type error, using default value" print "type error for '%s': using default value"%key
out = default out = default
return out return out

View File

@ -113,22 +113,33 @@ class Wallet:
while not self.is_up_to_date(): time.sleep(0.1) while not self.is_up_to_date(): time.sleep(0.1)
def import_key(self, keypair, password): def import_key(self, keypair, password):
address, key = keypair.split(':')
address, sec = keypair.split(':')
if not self.is_valid(address): if not self.is_valid(address):
raise BaseException('Invalid Bitcoin address') raise BaseException('Invalid Bitcoin address')
if address in self.all_addresses(): if address in self.all_addresses():
raise BaseException('Address already in wallet') raise BaseException('Address already in wallet')
b = ASecretToSecret( key )
if not b: # rebuild public key from private key, compressed or uncompressed
raise BaseException('Unsupported key format') pkey = regenerate_key(sec)
secexp = int( b.encode('hex'), 16) if not pkey:
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 ) return False
# figure out if private key is compressed
compressed = is_compressed(sec)
# rebuild private and public key from regenerated secret
private_key = GetPrivKey(pkey, compressed)
public_key = GetPubKey(pkey, compressed)
addr = public_key_to_bc_address(public_key)
# sanity check # sanity check
public_key = private_key.get_verifying_key() if not address == addr :
if not address == public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ):
raise BaseException('Address does not match private key') raise BaseException('Address does not match private key')
self.imported_keys[address] = self.pw_encode( key, password )
# store the originally requested keypair into the imported keys table
self.imported_keys[address] = self.pw_encode(sec, password )
def new_seed(self, password): def new_seed(self, password):
seed = "%032x"%ecdsa.util.randrange( pow(2,128) ) seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
@ -172,19 +183,23 @@ class Wallet:
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) ) return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
def get_private_key_base58(self, address, password): def get_private_key_base58(self, address, password):
pk = self.get_private_key(address, password) secexp, compressed = self.get_private_key(address, password)
if pk is None: return None if secexp is None: return None
return SecretToASecret( pk ) pk = number_to_string( secexp, generator_secp256k1.order() )
return SecretToASecret( pk, compressed )
def get_private_key(self, address, password): def get_private_key(self, address, password):
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """ """ Privatekey(type,n) = Master_private_key + H(n|S|type) """
order = generator_secp256k1.order() order = generator_secp256k1.order()
if address in self.imported_keys.keys(): if address in self.imported_keys.keys():
b = self.pw_decode( self.imported_keys[address], password ) sec = self.pw_decode( self.imported_keys[address], password )
if not b: return None if not sec: return None, None
b = ASecretToSecret( b )
secexp = int( b.encode('hex'), 16) pkey = regenerate_key(sec)
compressed = is_compressed(sec)
secexp = pkey.secret
else: else:
if address in self.addresses: if address in self.addresses:
n = self.addresses.index(address) n = self.addresses.index(address)
@ -201,20 +216,21 @@ class Wallet:
if not seed: return None if not seed: return None
secexp = self.stretch_key(seed) secexp = self.stretch_key(seed)
secexp = ( secexp + self.get_sequence(n,for_change) ) % order secexp = ( secexp + self.get_sequence(n,for_change) ) % order
compressed = False
pk = number_to_string(secexp,order) return secexp, compressed
return pk
def msg_magic(self, message): def msg_magic(self, message):
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
def sign_message(self, address, message, password): def sign_message(self, address, message, password):
private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 ) secexp, compressed = self.get_private_key(address, password)
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
public_key = private_key.get_verifying_key() public_key = private_key.get_verifying_key()
signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string ) 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) assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
for i in range(4): for i in range(4):
sig = base64.b64encode( chr(27+i) + signature ) sig = base64.b64encode( chr(27 + i + (4 if compressed else 0)) + signature )
try: try:
self.verify_message( address, sig, message) self.verify_message( address, sig, message)
return sig return sig
@ -598,9 +614,13 @@ class Wallet:
s_inputs = [] s_inputs = []
for i in range(len(inputs)): for i in range(len(inputs)):
addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i] addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 ) secexp, compressed = self.get_private_key(addr, password)
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
public_key = private_key.get_verifying_key() public_key = private_key.get_verifying_key()
pubkey = public_key.to_string()
pkey = EC_KEY(secexp)
pubkey = GetPubKey(pkey, compressed)
tx = filter( raw_tx( inputs, outputs, for_sig = i ) ) tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) 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) assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)