Merge branch 'master' into qt-switch-gui
This commit is contained in:
commit
362057c738
1
TODOLIST
1
TODOLIST
|
@ -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
|
||||||
|
|
|
@ -43,6 +43,55 @@ 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]
|
||||||
|
|
||||||
|
|
||||||
|
# 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 #####################
|
############ functions from pywallet #####################
|
||||||
|
|
||||||
addrtype = 0
|
addrtype = 0
|
||||||
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
@ -44,8 +47,11 @@ class Exchanger(threading.Thread):
|
||||||
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"))
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -336,6 +337,9 @@ class ElectrumWindow(QMainWindow):
|
||||||
#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':
|
||||||
n = 3 if self.wallet.seed else 2
|
n = 3 if self.wallet.seed else 2
|
||||||
|
@ -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,6 +407,14 @@ 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)
|
||||||
|
@ -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)
|
||||||
|
@ -1679,6 +1702,11 @@ class ElectrumWindow(QMainWindow):
|
||||||
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'))
|
||||||
|
|
||||||
|
|
17
lib/i18n.py
17
lib/i18n.py
|
@ -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')
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -113,21 +113,32 @@ 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):
|
||||||
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue