accounts
This commit is contained in:
parent
c871a79582
commit
e84d087a64
|
@ -415,8 +415,13 @@ def CKD_prime(K, c, n):
|
||||||
class DeterministicSequence:
|
class DeterministicSequence:
|
||||||
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
||||||
|
|
||||||
def __init__(self, master_public_key):
|
def __init__(self, master_public_key, mpk2 = None):
|
||||||
self.master_public_key = master_public_key
|
self.master_public_key = master_public_key
|
||||||
|
if mpk2:
|
||||||
|
self.mpk2 = mpk2
|
||||||
|
self.is_p2sh = True
|
||||||
|
else:
|
||||||
|
self.is_p2sh = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_seed(klass, seed):
|
def from_seed(klass, seed):
|
||||||
|
@ -437,6 +442,27 @@ class DeterministicSequence:
|
||||||
def get_sequence(self, n, for_change):
|
def get_sequence(self, n, for_change):
|
||||||
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_address(self, for_change, n):
|
||||||
|
if not self.is_p2sh:
|
||||||
|
pubkey = self.get_pubkey(n, for_change)
|
||||||
|
address = public_key_to_bc_address( pubkey.decode('hex') )
|
||||||
|
else:
|
||||||
|
pubkey1 = self.get_pubkey(n, for_change)
|
||||||
|
pubkey2 = self.get_pubkey2(n, for_change)
|
||||||
|
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
|
||||||
|
return address
|
||||||
|
|
||||||
|
#sec = self.p2sh_sequence.get_private_key(n, for_change, seed)
|
||||||
|
#addr = hash_160_to_bc_address(hash_160(txin["redeemScript"].decode('hex')), 5)
|
||||||
|
|
||||||
|
def get_pubkey2(self, n, for_change):
|
||||||
|
curve = SECP256k1
|
||||||
|
z = string_to_number( Hash( "%d:%d:"%(n, for_change) + self.mpk2.decode('hex') ) )
|
||||||
|
master_public_key = ecdsa.VerifyingKey.from_string( self.mpk2.decode('hex'), curve = SECP256k1 )
|
||||||
|
pubkey_point = master_public_key.pubkey.point + z*curve.generator
|
||||||
|
public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
|
||||||
|
return '04' + public_key2.to_string().encode('hex')
|
||||||
|
|
||||||
def get_pubkey(self, n, for_change):
|
def get_pubkey(self, n, for_change):
|
||||||
curve = SECP256k1
|
curve = SECP256k1
|
||||||
z = self.get_sequence(n, for_change)
|
z = self.get_sequence(n, for_change)
|
||||||
|
@ -467,15 +493,26 @@ class DeterministicSequence:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def add_input_info(self, txin, account, is_change, n):
|
||||||
|
|
||||||
|
txin['electrumKeyID'] = (account, is_change, n) # used by the server to find the key
|
||||||
|
if not self.p2sh:
|
||||||
|
txin['pubkeysig'] = [(None, None)]
|
||||||
|
pk_addr = txin['address']
|
||||||
|
else:
|
||||||
|
pubkey1 = self.get_pubkey(n, is_change)
|
||||||
|
pubkey2 = self.get_pubkey2(n, is_change)
|
||||||
|
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
|
||||||
|
txin['redeemScript'] = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
|
||||||
|
return pk_addr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################################## transactions
|
################################## transactions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction:
|
class Transaction:
|
||||||
|
|
||||||
def __init__(self, raw):
|
def __init__(self, raw):
|
||||||
|
@ -745,8 +782,24 @@ class Transaction:
|
||||||
"hex":self.raw,
|
"hex":self.raw,
|
||||||
"complete":self.is_complete
|
"complete":self.is_complete
|
||||||
}
|
}
|
||||||
if not self.is_complete and self.input_info:
|
if not self.is_complete:
|
||||||
|
extras = []
|
||||||
|
for i in self.inputs:
|
||||||
|
e = { 'txid':i['tx_hash'], 'vout':i['index'],
|
||||||
|
'scriptPubKey':i.get('raw_output_script'),
|
||||||
|
'electrumKeyID':i.get('electrumKeyID'),
|
||||||
|
'redeemScript':i.get('redeemScript'),
|
||||||
|
'signatures':i.get('signatures'),
|
||||||
|
'pubkeys':i.get('pubkeys'),
|
||||||
|
}
|
||||||
|
extras.append(e)
|
||||||
|
self.input_info = extras
|
||||||
|
|
||||||
|
if self.input_info:
|
||||||
out['input_info'] = json.dumps(self.input_info).replace(' ','')
|
out['input_info'] = json.dumps(self.input_info).replace(' ','')
|
||||||
|
|
||||||
|
|
||||||
|
print "out", out
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -387,7 +387,7 @@ def ok_cancel_buttons(dialog):
|
||||||
|
|
||||||
|
|
||||||
default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
|
default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
|
||||||
"receive":[[310],[50,310,200,130,130],[50,310,200,130,130]] }
|
"receive":[[310],[310,200,130,130],[310,200,130,130]] }
|
||||||
|
|
||||||
class ElectrumWindow(QMainWindow):
|
class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
|
@ -674,7 +674,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
self.recv_changed(item)
|
self.recv_changed(item)
|
||||||
|
|
||||||
if column == 3:
|
if column == 2:
|
||||||
address = str( item.text(column_addr) )
|
address = str( item.text(column_addr) )
|
||||||
text = str( item.text(3) )
|
text = str( item.text(3) )
|
||||||
try:
|
try:
|
||||||
|
@ -945,7 +945,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
if label:
|
if label:
|
||||||
self.wallet.labels[tx.hash()] = label
|
self.wallet.labels[tx.hash()] = label
|
||||||
|
|
||||||
if self.wallet.seed:
|
if tx.is_complete:
|
||||||
h = self.wallet.send_tx(tx)
|
h = self.wallet.send_tx(tx)
|
||||||
waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
|
waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
|
||||||
status, msg = self.wallet.receive_tx( h )
|
status, msg = self.wallet.receive_tx( h )
|
||||||
|
@ -966,6 +966,8 @@ class ElectrumWindow(QMainWindow):
|
||||||
QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
|
QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def set_url(self, url):
|
def set_url(self, url):
|
||||||
payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
|
payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
|
||||||
self.tabs.setCurrentIndex(1)
|
self.tabs.setCurrentIndex(1)
|
||||||
|
@ -1046,11 +1048,11 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
|
|
||||||
def create_receive_tab(self):
|
def create_receive_tab(self):
|
||||||
l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
|
l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
|
||||||
l.setContextMenuPolicy(Qt.CustomContextMenu)
|
l.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
l.customContextMenuRequested.connect(self.create_receive_menu)
|
l.customContextMenuRequested.connect(self.create_receive_menu)
|
||||||
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
|
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
|
||||||
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
|
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
|
||||||
self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
|
self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
|
||||||
self.receive_list = l
|
self.receive_list = l
|
||||||
self.receive_buttons_hbox = hbox
|
self.receive_buttons_hbox = hbox
|
||||||
|
@ -1069,7 +1071,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
def save_column_widths(self):
|
def save_column_widths(self):
|
||||||
if self.receive_tab_mode == 0:
|
if self.receive_tab_mode == 0:
|
||||||
widths = [ self.receive_list.columnWidth(1) ]
|
widths = [ self.receive_list.columnWidth(0) ]
|
||||||
else:
|
else:
|
||||||
widths = []
|
widths = []
|
||||||
for i in range(self.receive_list.columnCount() -1):
|
for i in range(self.receive_list.columnCount() -1):
|
||||||
|
@ -1116,7 +1118,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
item = self.receive_list.itemAt(position)
|
item = self.receive_list.itemAt(position)
|
||||||
if not item: return
|
if not item: return
|
||||||
addr = unicode(item.text(1))
|
addr = unicode(item.text(0))
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
|
menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
|
||||||
if self.receive_tab_mode == 2:
|
if self.receive_tab_mode == 2:
|
||||||
|
@ -1186,13 +1188,9 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
|
|
||||||
def update_receive_item(self, item):
|
def update_receive_item(self, item):
|
||||||
address = str( item.data(1,0).toString() )
|
address = str(item.data(0,0).toString())
|
||||||
|
|
||||||
flags = self.wallet.get_address_flags(address)
|
|
||||||
item.setData(0,0,flags)
|
|
||||||
|
|
||||||
label = self.wallet.labels.get(address,'')
|
label = self.wallet.labels.get(address,'')
|
||||||
item.setData(2,0,label)
|
item.setData(1,0,label)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
amount, currency = self.wallet.requested_amounts.get(address, (None, None))
|
amount, currency = self.wallet.requested_amounts.get(address, (None, None))
|
||||||
|
@ -1200,44 +1198,52 @@ class ElectrumWindow(QMainWindow):
|
||||||
amount, currency = None, None
|
amount, currency = None, None
|
||||||
|
|
||||||
amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
|
amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
|
||||||
item.setData(3,0,amount_str)
|
item.setData(2,0,amount_str)
|
||||||
|
|
||||||
c, u = self.wallet.get_addr_balance(address)
|
c, u = self.wallet.get_addr_balance(address)
|
||||||
balance = format_satoshis( c + u, False, self.wallet.num_zeros )
|
balance = format_satoshis( c + u, False, self.wallet.num_zeros )
|
||||||
item.setData(4,0,balance)
|
item.setData(3,0,balance)
|
||||||
|
|
||||||
if self.receive_tab_mode == 1:
|
if self.receive_tab_mode == 1:
|
||||||
if address in self.wallet.frozen_addresses:
|
if address in self.wallet.frozen_addresses:
|
||||||
item.setBackgroundColor(1, QColor('lightblue'))
|
item.setBackgroundColor(0, QColor('lightblue'))
|
||||||
elif address in self.wallet.prioritized_addresses:
|
elif address in self.wallet.prioritized_addresses:
|
||||||
item.setBackgroundColor(1, QColor('lightgreen'))
|
item.setBackgroundColor(0, QColor('lightgreen'))
|
||||||
|
|
||||||
|
|
||||||
def update_receive_tab(self):
|
def update_receive_tab(self):
|
||||||
l = self.receive_list
|
l = self.receive_list
|
||||||
|
|
||||||
l.clear()
|
l.clear()
|
||||||
l.setColumnHidden(0, not self.receive_tab_mode == 1)
|
l.setColumnHidden(2, not self.receive_tab_mode == 2)
|
||||||
l.setColumnHidden(3, not self.receive_tab_mode == 2)
|
l.setColumnHidden(3, self.receive_tab_mode == 0)
|
||||||
l.setColumnHidden(4, self.receive_tab_mode == 0)
|
l.setColumnHidden(4, not self.receive_tab_mode == 1)
|
||||||
l.setColumnHidden(5, not self.receive_tab_mode == 1)
|
|
||||||
if self.receive_tab_mode == 0:
|
if self.receive_tab_mode == 0:
|
||||||
width = self.column_widths['receive'][0][0]
|
width = self.column_widths['receive'][0][0]
|
||||||
l.setColumnWidth(1, width)
|
l.setColumnWidth(0, width)
|
||||||
else:
|
else:
|
||||||
for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
|
for i,width in enumerate(self.column_widths['receive'][self.receive_tab_mode]):
|
||||||
l.setColumnWidth(i, width)
|
l.setColumnWidth(i, width)
|
||||||
|
|
||||||
gap = 0
|
|
||||||
|
for k, account in self.wallet.accounts.items():
|
||||||
|
name = account.get('name',str(k))
|
||||||
|
account_item = QTreeWidgetItem( [ name, '', '', '', ''] )
|
||||||
|
l.addTopLevelItem(account_item)
|
||||||
|
account_item.setExpanded(True)
|
||||||
|
|
||||||
|
for is_change in [0,1]:
|
||||||
|
name = "Receiving" if not is_change else "Change"
|
||||||
|
seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
|
||||||
|
account_item.addChild(seq_item)
|
||||||
|
if not is_change: seq_item.setExpanded(True)
|
||||||
is_red = False
|
is_red = False
|
||||||
for address in self.wallet.all_addresses():
|
gap = 0
|
||||||
|
|
||||||
if self.wallet.is_change(address) and self.receive_tab_mode != 1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
for address in account[is_change]:
|
||||||
h = self.wallet.history.get(address,[])
|
h = self.wallet.history.get(address,[])
|
||||||
|
|
||||||
if address in self.wallet.addresses:
|
if not is_change:
|
||||||
if h == []:
|
if h == []:
|
||||||
gap += 1
|
gap += 1
|
||||||
if gap > self.wallet.gap_limit:
|
if gap > self.wallet.gap_limit:
|
||||||
|
@ -1246,14 +1252,27 @@ class ElectrumWindow(QMainWindow):
|
||||||
gap = 0
|
gap = 0
|
||||||
|
|
||||||
num_tx = '*' if h == ['*'] else "%d"%len(h)
|
num_tx = '*' if h == ['*'] else "%d"%len(h)
|
||||||
item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
|
item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
|
||||||
item.setFont(0, QFont(MONOSPACE_FONT))
|
item.setFont(0, QFont(MONOSPACE_FONT))
|
||||||
item.setFont(1, QFont(MONOSPACE_FONT))
|
item.setFont(2, QFont(MONOSPACE_FONT))
|
||||||
item.setFont(3, QFont(MONOSPACE_FONT))
|
|
||||||
self.update_receive_item(item)
|
self.update_receive_item(item)
|
||||||
if is_red and address in self.wallet.addresses:
|
if is_red and not is_change:
|
||||||
item.setBackgroundColor(1, QColor('red'))
|
item.setBackgroundColor(1, QColor('red'))
|
||||||
l.addTopLevelItem(item)
|
seq_item.addChild(item)
|
||||||
|
|
||||||
|
if self.wallet.imported_keys:
|
||||||
|
account_item = QTreeWidgetItem( [ "Imported", '', '', '', ''] )
|
||||||
|
l.addTopLevelItem(account_item)
|
||||||
|
account_item.setExpanded(True)
|
||||||
|
for address in self.wallet.imported_keys.keys():
|
||||||
|
item = QTreeWidgetItem( [ address, '', '', '', ''] )
|
||||||
|
item.setFont(0, QFont(MONOSPACE_FONT))
|
||||||
|
item.setFont(2, QFont(MONOSPACE_FONT))
|
||||||
|
self.update_receive_item(item)
|
||||||
|
if is_red and not is_change:
|
||||||
|
item.setBackgroundColor(1, QColor('red'))
|
||||||
|
account_item.addChild(item)
|
||||||
|
|
||||||
|
|
||||||
# we use column 1 because column 0 may be hidden
|
# we use column 1 because column 0 may be hidden
|
||||||
l.setCurrentItem(l.topLevelItem(0),1)
|
l.setCurrentItem(l.topLevelItem(0),1)
|
||||||
|
@ -1391,8 +1410,8 @@ class ElectrumWindow(QMainWindow):
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def show_seed_dialog(wallet, parent=None):
|
def show_seed_dialog(self, wallet, parent=None):
|
||||||
if not wallet.seed:
|
if not wallet.seed:
|
||||||
QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
|
QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
|
||||||
return
|
return
|
||||||
|
@ -1410,6 +1429,10 @@ class ElectrumWindow(QMainWindow):
|
||||||
QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
|
QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.show_seed(seed)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def show_seed(self, seed):
|
||||||
dialog = QDialog(None)
|
dialog = QDialog(None)
|
||||||
dialog.setModal(1)
|
dialog.setModal(1)
|
||||||
dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
|
dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
|
||||||
|
@ -1625,8 +1648,8 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.qr_window.setVisible(False)
|
self.qr_window.setVisible(False)
|
||||||
|
|
||||||
#self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
|
#self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
|
||||||
self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
|
self.receive_list.setColumnHidden(2, self.qr_window is None or not self.qr_window.isVisible())
|
||||||
self.receive_list.setColumnWidth(2, 200)
|
self.receive_list.setColumnWidth(1, 200)
|
||||||
|
|
||||||
|
|
||||||
def question(self, msg):
|
def question(self, msg):
|
||||||
|
|
202
lib/wallet.py
202
lib/wallet.py
|
@ -80,8 +80,6 @@ class Wallet:
|
||||||
self.fee = int(config.get('fee',100000))
|
self.fee = int(config.get('fee',100000))
|
||||||
self.num_zeros = int(config.get('num_zeros',0))
|
self.num_zeros = int(config.get('num_zeros',0))
|
||||||
self.use_encryption = config.get('use_encryption', False)
|
self.use_encryption = config.get('use_encryption', False)
|
||||||
self.addresses = config.get('addresses', []) # receiving addresses visible for user
|
|
||||||
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
|
||||||
self.seed = config.get('seed', '') # encrypted
|
self.seed = config.get('seed', '') # encrypted
|
||||||
self.labels = config.get('labels', {})
|
self.labels = config.get('labels', {})
|
||||||
self.aliases = config.get('aliases', {}) # aliases for addresses
|
self.aliases = config.get('aliases', {}) # aliases for addresses
|
||||||
|
@ -93,9 +91,15 @@ class Wallet:
|
||||||
self.imported_keys = config.get('imported_keys',{})
|
self.imported_keys = config.get('imported_keys',{})
|
||||||
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
||||||
self.tx_height = config.get('tx_height',{})
|
self.tx_height = config.get('tx_height',{})
|
||||||
|
self.requested_amounts = config.get('requested_amounts',{})
|
||||||
|
self.accounts = config.get('accounts', {}) # this should not include public keys
|
||||||
|
self.sequences = {}
|
||||||
|
|
||||||
|
mpk1 = self.config.get('master_public_key')
|
||||||
|
self.sequences[0] = DeterministicSequence(mpk1)
|
||||||
|
if self.accounts.get(0) is None:
|
||||||
|
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
|
||||||
|
|
||||||
master_public_key = config.get('master_public_key','')
|
|
||||||
self.sequence = DeterministicSequence(master_public_key)
|
|
||||||
|
|
||||||
self.transactions = {}
|
self.transactions = {}
|
||||||
tx = config.get('transactions',{})
|
tx = config.get('transactions',{})
|
||||||
|
@ -105,7 +109,6 @@ class Wallet:
|
||||||
print_msg("Warning: Cannot deserialize transactions. skipping")
|
print_msg("Warning: Cannot deserialize transactions. skipping")
|
||||||
|
|
||||||
|
|
||||||
self.requested_amounts = config.get('requested_amounts',{})
|
|
||||||
|
|
||||||
# not saved
|
# not saved
|
||||||
self.prevout_values = {} # my own transaction outputs
|
self.prevout_values = {} # my own transaction outputs
|
||||||
|
@ -162,21 +165,33 @@ class Wallet:
|
||||||
self.seed = seed
|
self.seed = seed
|
||||||
self.config.set_key('seed', self.seed, True)
|
self.config.set_key('seed', self.seed, True)
|
||||||
self.config.set_key('seed_version', self.seed_version, True)
|
self.config.set_key('seed_version', self.seed_version, True)
|
||||||
self.init_mpk(self.seed)
|
|
||||||
|
|
||||||
def init_mpk(self,seed):
|
self.init_main_account(self.seed)
|
||||||
|
|
||||||
|
|
||||||
|
def init_main_account(self, seed):
|
||||||
# public key
|
# public key
|
||||||
self.sequence = DeterministicSequence.from_seed(seed)
|
sequence = DeterministicSequence.from_seed(seed)
|
||||||
self.config.set_key('master_public_key', self.sequence.master_public_key, True)
|
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
|
||||||
|
self.sequences[0] = sequence
|
||||||
|
self.config.set_key('accounts', self.accounts, True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def all_addresses(self):
|
def all_addresses(self):
|
||||||
return self.addresses + self.change_addresses + self.imported_keys.keys()
|
o = self.imported_keys.keys()
|
||||||
|
for a in self.accounts.values():
|
||||||
|
o += a[0]
|
||||||
|
o += a[1]
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
def is_mine(self, address):
|
def is_mine(self, address):
|
||||||
return address in self.all_addresses()
|
return address in self.all_addresses()
|
||||||
|
|
||||||
def is_change(self, address):
|
def is_change(self, address):
|
||||||
return address in self.change_addresses
|
#return address in self.change_addresses
|
||||||
|
return False
|
||||||
|
|
||||||
def get_master_public_key(self):
|
def get_master_public_key(self):
|
||||||
return self.sequence.master_public_key
|
return self.sequence.master_public_key
|
||||||
|
@ -184,47 +199,40 @@ class Wallet:
|
||||||
def get_address_index(self, address):
|
def get_address_index(self, address):
|
||||||
if address in self.imported_keys.keys():
|
if address in self.imported_keys.keys():
|
||||||
raise BaseException("imported key")
|
raise BaseException("imported key")
|
||||||
|
for account in self.accounts.keys():
|
||||||
|
for for_change in [0,1]:
|
||||||
|
addresses = self.accounts[account][for_change]
|
||||||
|
for addr in addresses:
|
||||||
|
if address == addr:
|
||||||
|
return account, for_change, addresses.index(addr)
|
||||||
|
raise BaseException("not found")
|
||||||
|
|
||||||
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
|
|
||||||
return n,for_change
|
|
||||||
|
|
||||||
def get_public_key(self, address):
|
def get_public_key(self, address):
|
||||||
n, for_change = self.get_address_index(address)
|
account, n, for_change = self.get_address_index(address)
|
||||||
return self.sequence.get_pubkey(n, for_change)
|
return self.sequences[account].get_pubkey(n, for_change)
|
||||||
|
|
||||||
|
|
||||||
def decode_seed(self, password):
|
def decode_seed(self, password):
|
||||||
seed = pw_decode(self.seed, password)
|
seed = pw_decode(self.seed, password)
|
||||||
self.sequence.check_seed(seed)
|
self.sequences[0].check_seed(seed)
|
||||||
return seed
|
return seed
|
||||||
|
|
||||||
def get_private_key(self, address, password):
|
def get_private_key(self, address, password):
|
||||||
return self.get_private_keys([address], password)[address]
|
return self.get_private_keys([address], password).get(address)
|
||||||
|
|
||||||
def get_private_keys(self, addresses, password):
|
def get_private_keys(self, addresses, password):
|
||||||
# decode seed in any case, in order to test the password
|
# decode seed in any case, in order to test the password
|
||||||
seed = self.decode_seed(password)
|
seed = self.decode_seed(password)
|
||||||
secexp = self.sequence.stretch_key(seed)
|
secexp = self.sequences[0].stretch_key(seed)
|
||||||
out = {}
|
out = {}
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
if address in self.imported_keys.keys():
|
if address in self.imported_keys.keys():
|
||||||
pk = pw_decode( self.imported_keys[address], password )
|
out[address] = pw_decode( self.imported_keys[address], password )
|
||||||
else:
|
else:
|
||||||
if address in self.addresses:
|
account, for_change, n = self.get_address_index(address)
|
||||||
n = self.addresses.index(address)
|
if account == 0:
|
||||||
for_change = False
|
out[address] = self.sequences[0].get_private_key_from_stretched_exponent(n, for_change, secexp)
|
||||||
elif address in self.change_addresses:
|
|
||||||
n = self.change_addresses.index(address)
|
|
||||||
for_change = True
|
|
||||||
else:
|
|
||||||
raise BaseException("unknown address", address)
|
|
||||||
pk = self.sequence.get_private_key_from_stretched_exponent(n, for_change, secexp)
|
|
||||||
out[address] = pk
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,11 +269,11 @@ class Wallet:
|
||||||
|
|
||||||
# find the address:
|
# find the address:
|
||||||
if txin.get('electrumKeyID'):
|
if txin.get('electrumKeyID'):
|
||||||
n, for_change = txin.get('electrumKeyID')
|
account, for_change, n = txin.get('electrumKeyID')
|
||||||
sec = self.sequence.get_private_key(n, for_change, seed)
|
sec = self.sequences[account].get_private_key(n, for_change, seed)
|
||||||
address = address_from_private_key(sec)
|
addr = self.sequences[account].get_address(n, for_change)
|
||||||
txin['address'] = address
|
txin['address'] = addr
|
||||||
private_keys[address] = sec
|
private_keys[addr] = sec
|
||||||
|
|
||||||
elif txin.get("redeemScript"):
|
elif txin.get("redeemScript"):
|
||||||
txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
|
txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
|
||||||
|
@ -280,26 +288,24 @@ class Wallet:
|
||||||
|
|
||||||
tx.sign( private_keys )
|
tx.sign( private_keys )
|
||||||
|
|
||||||
|
|
||||||
def sign_message(self, address, message, password):
|
def sign_message(self, address, message, password):
|
||||||
sec = self.get_private_key(address, password)
|
sec = self.get_private_key(address, password)
|
||||||
key = regenerate_key(sec)
|
key = regenerate_key(sec)
|
||||||
compressed = is_compressed(sec)
|
compressed = is_compressed(sec)
|
||||||
return key.sign_message(message, compressed, address)
|
return key.sign_message(message, compressed, address)
|
||||||
|
|
||||||
def create_new_address(self, for_change):
|
|
||||||
n = len(self.change_addresses) if for_change else len(self.addresses)
|
def create_new_address(self, account, for_change):
|
||||||
address = self.get_new_address(n, for_change)
|
addresses = self.accounts[account][for_change]
|
||||||
if for_change:
|
n = len(addresses)
|
||||||
self.change_addresses.append(address)
|
address = self.get_new_address( account, for_change, n)
|
||||||
else:
|
self.accounts[account][for_change].append(address)
|
||||||
self.addresses.append(address)
|
|
||||||
self.history[address] = []
|
self.history[address] = []
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def get_new_address(self, n, for_change):
|
|
||||||
pubkey = self.sequence.get_pubkey(n, for_change)
|
def get_new_address(self, account, for_change, n):
|
||||||
address = public_key_to_bc_address( pubkey.decode('hex') )
|
return self.sequences[account].get_address(for_change, n)
|
||||||
print_msg( address )
|
print_msg( address )
|
||||||
return address
|
return address
|
||||||
|
|
||||||
|
@ -311,18 +317,22 @@ class Wallet:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif value >= self.min_acceptable_gap():
|
elif value >= self.min_acceptable_gap():
|
||||||
k = self.num_unused_trailing_addresses()
|
for key, account in self.accounts.items():
|
||||||
n = len(self.addresses) - k + value
|
addresses = account[0]
|
||||||
self.addresses = self.addresses[0:n]
|
k = self.num_unused_trailing_addresses(addresses)
|
||||||
|
n = len(addresses) - k + value
|
||||||
|
addresses = addresses[0:n]
|
||||||
|
self.accounts[key][0] = addresses
|
||||||
|
|
||||||
self.gap_limit = value
|
self.gap_limit = value
|
||||||
self.save()
|
self.save()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def num_unused_trailing_addresses(self):
|
def num_unused_trailing_addresses(self, addresses):
|
||||||
k = 0
|
k = 0
|
||||||
for a in self.addresses[::-1]:
|
for a in addresses[::-1]:
|
||||||
if self.history.get(a):break
|
if self.history.get(a):break
|
||||||
k = k + 1
|
k = k + 1
|
||||||
return k
|
return k
|
||||||
|
@ -331,8 +341,11 @@ class Wallet:
|
||||||
# fixme: this assumes wallet is synchronized
|
# fixme: this assumes wallet is synchronized
|
||||||
n = 0
|
n = 0
|
||||||
nmax = 0
|
nmax = 0
|
||||||
k = self.num_unused_trailing_addresses()
|
|
||||||
for a in self.addresses[0:-k]:
|
for account in self.accounts.values():
|
||||||
|
addresses = account[0]
|
||||||
|
k = self.num_unused_trailing_addresses(addresses)
|
||||||
|
for a in addresses[0:-k]:
|
||||||
if self.history.get(a):
|
if self.history.get(a):
|
||||||
n = 0
|
n = 0
|
||||||
else:
|
else:
|
||||||
|
@ -356,26 +369,32 @@ class Wallet:
|
||||||
return age > 2
|
return age > 2
|
||||||
|
|
||||||
|
|
||||||
def synchronize_sequence(self, addresses, n, for_change):
|
def synchronize_sequence(self, account, for_change):
|
||||||
|
limit = self.gap_limit_for_change if for_change else self.gap_limit
|
||||||
|
addresses = self.accounts[account][for_change]
|
||||||
new_addresses = []
|
new_addresses = []
|
||||||
while True:
|
while True:
|
||||||
if len(self.addresses) < n:
|
if len(addresses) < limit:
|
||||||
new_addresses.append( self.create_new_address(for_change) )
|
new_addresses.append( self.create_new_address(account, for_change) )
|
||||||
continue
|
continue
|
||||||
if map( lambda a: self.address_is_old(a), addresses[-n:] ) == n*[False]:
|
if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
new_addresses.append( self.create_new_address(for_change) )
|
new_addresses.append( self.create_new_address(account, for_change) )
|
||||||
return new_addresses
|
return new_addresses
|
||||||
|
|
||||||
|
|
||||||
|
def synchronize_account(self, account):
|
||||||
|
new = []
|
||||||
|
new += self.synchronize_sequence(account, 0)
|
||||||
|
new += self.synchronize_sequence(account, 1)
|
||||||
|
return new
|
||||||
|
|
||||||
def synchronize(self):
|
def synchronize(self):
|
||||||
if not self.sequence.master_public_key:
|
new = []
|
||||||
return []
|
for account in self.accounts.keys():
|
||||||
new_addresses = []
|
new += self.synchronize_account(account)
|
||||||
new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False)
|
return new
|
||||||
new_addresses += self.synchronize_sequence(self.change_addresses, self.gap_limit_for_change, True)
|
|
||||||
return new_addresses
|
|
||||||
|
|
||||||
|
|
||||||
def is_found(self):
|
def is_found(self):
|
||||||
|
@ -506,14 +525,27 @@ class Wallet:
|
||||||
u += v
|
u += v
|
||||||
return c, u
|
return c, u
|
||||||
|
|
||||||
def get_balance(self):
|
def get_account_addresses(self, a):
|
||||||
|
ac = self.accounts[a]
|
||||||
|
return ac[0] + ac[1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_balance(self, account):
|
||||||
conf = unconf = 0
|
conf = unconf = 0
|
||||||
for addr in self.all_addresses():
|
for addr in self.get_account_addresses(account):
|
||||||
c, u = self.get_addr_balance(addr)
|
c, u = self.get_addr_balance(addr)
|
||||||
conf += c
|
conf += c
|
||||||
unconf += u
|
unconf += u
|
||||||
return conf, unconf
|
return conf, unconf
|
||||||
|
|
||||||
|
def get_balance(self):
|
||||||
|
cc = uu = 0
|
||||||
|
for a in self.accounts.keys():
|
||||||
|
c, u = self.get_account_balance(a)
|
||||||
|
cc += c
|
||||||
|
uu += u
|
||||||
|
return cc, uu
|
||||||
|
|
||||||
|
|
||||||
def get_unspent_coins(self, domain=None):
|
def get_unspent_coins(self, domain=None):
|
||||||
coins = []
|
coins = []
|
||||||
|
@ -557,7 +589,7 @@ class Wallet:
|
||||||
addr = item.get('address')
|
addr = item.get('address')
|
||||||
v = item.get('value')
|
v = item.get('value')
|
||||||
total += v
|
total += v
|
||||||
item['pubkeysig'] = [(None, None)]
|
|
||||||
inputs.append( item )
|
inputs.append( item )
|
||||||
fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
|
fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
|
||||||
if total >= amount + fee: break
|
if total >= amount + fee: break
|
||||||
|
@ -718,15 +750,18 @@ class Wallet:
|
||||||
outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
|
outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
|
||||||
|
|
||||||
tx = Transaction.from_io(inputs, outputs)
|
tx = Transaction.from_io(inputs, outputs)
|
||||||
|
|
||||||
|
pk_addresses = []
|
||||||
for i in range(len(tx.inputs)):
|
for i in range(len(tx.inputs)):
|
||||||
addr = tx.inputs[i]['address']
|
txin = tx.inputs[i]
|
||||||
n, is_change = self.get_address_index(addr)
|
account, is_change, n = self.get_address_index(txin['address'])
|
||||||
tx.input_info[i]['electrumKeyID'] = (n, is_change)
|
pk_addr = self.sequences[account].add_input_info(txin, account, is_change, n)
|
||||||
|
pk_addresses.append(pk_addr)
|
||||||
|
|
||||||
if not self.seed:
|
# get all private keys at once.
|
||||||
return tx
|
private_keys = self.get_private_keys(pk_addresses, password)
|
||||||
|
print "private keys", private_keys
|
||||||
self.sign_tx(tx, password)
|
tx.sign(private_keys)
|
||||||
|
|
||||||
for address, x in outputs:
|
for address, x in outputs:
|
||||||
if address not in self.addressbook and not self.is_mine(address):
|
if address not in self.addressbook and not self.is_mine(address):
|
||||||
|
@ -734,13 +769,7 @@ class Wallet:
|
||||||
|
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
def sign_tx(self, tx, password):
|
|
||||||
private_keys = {}
|
|
||||||
for txin in tx.inputs:
|
|
||||||
addr = txin['address']
|
|
||||||
sec = self.get_private_key(addr, password)
|
|
||||||
private_keys[addr] = sec
|
|
||||||
tx.sign(private_keys)
|
|
||||||
|
|
||||||
def sendtx(self, tx):
|
def sendtx(self, tx):
|
||||||
# synchronous
|
# synchronous
|
||||||
|
@ -954,8 +983,7 @@ class Wallet:
|
||||||
'use_encryption': self.use_encryption,
|
'use_encryption': self.use_encryption,
|
||||||
'use_change': self.use_change,
|
'use_change': self.use_change,
|
||||||
'fee': self.fee,
|
'fee': self.fee,
|
||||||
'addresses': self.addresses,
|
'accounts': self.accounts,
|
||||||
'change_addresses': self.change_addresses,
|
|
||||||
'addr_history': self.history,
|
'addr_history': self.history,
|
||||||
'labels': self.labels,
|
'labels': self.labels,
|
||||||
'contacts': self.addressbook,
|
'contacts': self.addressbook,
|
||||||
|
|
Loading…
Reference in New Issue