Merge branch 'master' of git://github.com/spesmilo/electrum
This commit is contained in:
commit
bbb7d6be92
24
electrum
24
electrum
|
@ -247,7 +247,7 @@ if __name__ == '__main__':
|
||||||
wallet.gap_limit = gap
|
wallet.gap_limit = gap
|
||||||
if len(seed) == 128:
|
if len(seed) == 128:
|
||||||
wallet.seed = ''
|
wallet.seed = ''
|
||||||
wallet.master_public_key = seed
|
wallet.sequence.master_public_key = seed
|
||||||
else:
|
else:
|
||||||
wallet.init_seed(str(seed))
|
wallet.init_seed(str(seed))
|
||||||
|
|
||||||
|
@ -332,7 +332,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
if len(seed) == 128:
|
if len(seed) == 128:
|
||||||
wallet.seed = None
|
wallet.seed = None
|
||||||
wallet.master_public_key = seed
|
wallet.sequence.master_public_key = seed
|
||||||
else:
|
else:
|
||||||
wallet.seed = str(seed)
|
wallet.seed = str(seed)
|
||||||
wallet.init_mpk( wallet.seed )
|
wallet.init_mpk( wallet.seed )
|
||||||
|
@ -488,12 +488,12 @@ if __name__ == '__main__':
|
||||||
except:
|
except:
|
||||||
sys.exit("Error: Error with seed file")
|
sys.exit("Error: Error with seed file")
|
||||||
|
|
||||||
mpk = wallet.master_public_key
|
mpk = wallet.get_master_public_key()
|
||||||
wallet.seed = seed
|
wallet.seed = seed
|
||||||
wallet.imported_keys = imported_keys
|
wallet.imported_keys = imported_keys
|
||||||
wallet.use_encryption = False
|
wallet.use_encryption = False
|
||||||
wallet.init_mpk(seed)
|
wallet.init_mpk(seed)
|
||||||
if mpk == wallet.master_public_key:
|
if mpk == wallet.get_master_public_key():
|
||||||
wallet.save()
|
wallet.save()
|
||||||
print_msg("Done: " + wallet.config.path)
|
print_msg("Done: " + wallet.config.path)
|
||||||
else:
|
else:
|
||||||
|
@ -501,7 +501,16 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
elif cmd == 'validateaddress':
|
elif cmd == 'validateaddress':
|
||||||
addr = args[1]
|
addr = args[1]
|
||||||
print_msg(wallet.is_valid(addr))
|
is_valid = wallet.is_valid(addr)
|
||||||
|
out = { 'isvalid':is_valid }
|
||||||
|
if is_valid:
|
||||||
|
is_mine = wallet.is_mine(addr)
|
||||||
|
out['address'] = addr
|
||||||
|
out['ismine'] = is_mine
|
||||||
|
if is_mine:
|
||||||
|
out['pubkey'] = wallet.get_public_key(addr)
|
||||||
|
|
||||||
|
print_json(out)
|
||||||
|
|
||||||
elif cmd == 'balance':
|
elif cmd == 'balance':
|
||||||
try:
|
try:
|
||||||
|
@ -779,12 +788,11 @@ if __name__ == '__main__':
|
||||||
private_keys = pk
|
private_keys = pk
|
||||||
|
|
||||||
tx.sign( private_keys )
|
tx.sign( private_keys )
|
||||||
print_msg(tx)
|
print_json({ "hex":str(tx),"complete":tx.is_complete})
|
||||||
|
|
||||||
|
|
||||||
elif cmd == 'listunspent':
|
elif cmd == 'listunspent':
|
||||||
unspent = map(lambda x: {"txid":x[0].split(':')[0],"vout":x[0].split(':')[1],"amount":x[1]*1.e-8}, wallet.prevout_values.items() )
|
print_json(wallet.get_unspent_coins())
|
||||||
print_json(unspent)
|
|
||||||
|
|
||||||
|
|
||||||
if cmd not in offline_commands and not options.offline:
|
if cmd not in offline_commands and not options.offline:
|
||||||
|
|
|
@ -398,7 +398,57 @@ def CKD_prime(K, c, n):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DeterministicSequence:
|
||||||
|
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
||||||
|
|
||||||
|
def __init__(self, master_public_key):
|
||||||
|
self.master_public_key = master_public_key
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_seed(klass, seed):
|
||||||
|
curve = SECP256k1
|
||||||
|
secexp = klass.stretch_key(seed)
|
||||||
|
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||||
|
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
|
||||||
|
self = klass(master_public_key)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
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.decode('hex') ) )
|
||||||
|
|
||||||
|
def get_pubkey(self, n, for_change):
|
||||||
|
curve = SECP256k1
|
||||||
|
z = self.get_sequence(n, for_change)
|
||||||
|
master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.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_private_key(self, n, for_change, seed):
|
||||||
|
order = generator_secp256k1.order()
|
||||||
|
secexp = self.stretch_key(seed)
|
||||||
|
secexp = ( secexp + self.get_sequence(n,for_change) ) % order
|
||||||
|
pk = number_to_string( secexp, generator_secp256k1.order() )
|
||||||
|
compressed = False
|
||||||
|
return SecretToASecret( pk, compressed )
|
||||||
|
|
||||||
|
def check_seed(self, seed):
|
||||||
|
curve = SECP256k1
|
||||||
|
secexp = self.stretch_key(seed)
|
||||||
|
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||||
|
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
|
||||||
|
if master_public_key != self.master_public_key:
|
||||||
|
print_error('invalid password (mpk)')
|
||||||
|
raise BaseException('Invalid password')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
################################## transactions
|
################################## transactions
|
||||||
|
|
||||||
|
@ -535,6 +585,7 @@ class Transaction:
|
||||||
|
|
||||||
for i in range(len(self.inputs)):
|
for i in range(len(self.inputs)):
|
||||||
txin = self.inputs[i]
|
txin = self.inputs[i]
|
||||||
|
tx_for_sig = raw_tx( self.inputs, self.outputs, for_sig = i )
|
||||||
|
|
||||||
if txin.get('redeemScript'):
|
if txin.get('redeemScript'):
|
||||||
# 1 parse the redeem script
|
# 1 parse the redeem script
|
||||||
|
@ -549,26 +600,43 @@ class Transaction:
|
||||||
pubkey = GetPubKey(pkey.pubkey, compressed)
|
pubkey = GetPubKey(pkey.pubkey, compressed)
|
||||||
keypairs[ pubkey.encode('hex') ] = sec
|
keypairs[ pubkey.encode('hex') ] = sec
|
||||||
|
|
||||||
# list of signatures
|
# list of already existing signatures
|
||||||
signatures = txin.get("signatures",[])
|
signatures = txin.get("signatures",[])
|
||||||
|
found = False
|
||||||
|
complete = True
|
||||||
|
|
||||||
# check if we have a key corresponding to the redeem script
|
# check if we have a key corresponding to the redeem script
|
||||||
for pubkey, privkey in keypairs.items():
|
for pubkey in redeem_pubkeys:
|
||||||
if pubkey in redeem_pubkeys:
|
public_key = ecdsa.VerifyingKey.from_string(pubkey[2:].decode('hex'), curve = SECP256k1)
|
||||||
|
|
||||||
|
for s in signatures:
|
||||||
|
try:
|
||||||
|
public_key.verify_digest( s.decode('hex')[:-1], Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
|
||||||
|
break
|
||||||
|
except ecdsa.keys.BadSignatureError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if pubkey in keypairs.keys():
|
||||||
# add signature
|
# add signature
|
||||||
|
sec = keypairs[pubkey]
|
||||||
compressed = is_compressed(sec)
|
compressed = is_compressed(sec)
|
||||||
pkey = regenerate_key(sec)
|
pkey = regenerate_key(sec)
|
||||||
secexp = pkey.secret
|
secexp = pkey.secret
|
||||||
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||||
public_key = private_key.get_verifying_key()
|
public_key = private_key.get_verifying_key()
|
||||||
|
sig = private_key.sign_digest( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
|
||||||
tx = raw_tx( self.inputs, self.outputs, for_sig = i )
|
assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_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)
|
|
||||||
signatures.append( sig.encode('hex') )
|
signatures.append( sig.encode('hex') )
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
raise BaseException("public key not found", keypairs.keys(), redeem_pubkeys)
|
||||||
|
|
||||||
# for p2sh, pubkeysig is a tuple (may be incomplete)
|
# for p2sh, pubkeysig is a tuple (may be incomplete)
|
||||||
self.inputs[i]["signatures"] = signatures
|
self.inputs[i]["signatures"] = signatures
|
||||||
|
self.is_complete = complete
|
||||||
|
|
||||||
else:
|
else:
|
||||||
sec = private_keys[txin['address']]
|
sec = private_keys[txin['address']]
|
||||||
|
@ -580,11 +648,11 @@ class Transaction:
|
||||||
public_key = private_key.get_verifying_key()
|
public_key = private_key.get_verifying_key()
|
||||||
pkey = EC_KEY(secexp)
|
pkey = EC_KEY(secexp)
|
||||||
pubkey = GetPubKey(pkey.pubkey, compressed)
|
pubkey = GetPubKey(pkey.pubkey, compressed)
|
||||||
tx = raw_tx( self.inputs, self.outputs, for_sig = i )
|
sig = private_key.sign_digest( Hash( tx_for_sig.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_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
|
||||||
assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
|
|
||||||
|
|
||||||
self.inputs[i]["pubkeysig"] = [(pubkey, sig)]
|
self.inputs[i]["pubkeysig"] = [(pubkey, sig)]
|
||||||
|
self.is_complete = True
|
||||||
|
|
||||||
self.raw = raw_tx( self.inputs, self.outputs )
|
self.raw = raw_tx( self.inputs, self.outputs )
|
||||||
|
|
||||||
|
@ -598,9 +666,6 @@ class Transaction:
|
||||||
|
|
||||||
|
|
||||||
def has_address(self, addr):
|
def has_address(self, addr):
|
||||||
print self.inputs
|
|
||||||
print self.outputs
|
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
for txin in self.inputs:
|
for txin in self.inputs:
|
||||||
if addr == txin.get('address'):
|
if addr == txin.get('address'):
|
||||||
|
|
|
@ -315,7 +315,7 @@ def match_decoded(decoded, to_match):
|
||||||
if len(decoded) != len(to_match):
|
if len(decoded) != len(to_match):
|
||||||
return False;
|
return False;
|
||||||
for i in range(len(decoded)):
|
for i in range(len(decoded)):
|
||||||
if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4:
|
if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4 and decoded[i][0]>0:
|
||||||
continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.
|
continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.
|
||||||
if to_match[i] != decoded[i][0]:
|
if to_match[i] != decoded[i][0]:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1319,10 +1319,10 @@ class ElectrumWindow(QMainWindow):
|
||||||
dialog.setWindowTitle(_("Master Public Key"))
|
dialog.setWindowTitle(_("Master Public Key"))
|
||||||
|
|
||||||
main_text = QTextEdit()
|
main_text = QTextEdit()
|
||||||
main_text.setText(self.wallet.master_public_key)
|
main_text.setText(self.wallet.get_master_public_key())
|
||||||
main_text.setReadOnly(True)
|
main_text.setReadOnly(True)
|
||||||
main_text.setMaximumHeight(170)
|
main_text.setMaximumHeight(170)
|
||||||
qrw = QRCodeWidget(self.wallet.master_public_key, 6)
|
qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
|
||||||
|
|
||||||
ok_button = QPushButton(_("OK"))
|
ok_button = QPushButton(_("OK"))
|
||||||
ok_button.setDefault(True)
|
ok_button.setDefault(True)
|
||||||
|
|
196
lib/wallet.py
196
lib/wallet.py
|
@ -43,6 +43,27 @@ urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
||||||
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
|
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
|
||||||
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
|
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
|
||||||
|
|
||||||
|
def pw_encode(s, password):
|
||||||
|
if password:
|
||||||
|
secret = Hash(password)
|
||||||
|
return EncodeAES(secret, s)
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
def pw_decode(s, password):
|
||||||
|
if password is not None:
|
||||||
|
secret = Hash(password)
|
||||||
|
try:
|
||||||
|
d = DecodeAES(secret, s)
|
||||||
|
except:
|
||||||
|
raise BaseException('Invalid password')
|
||||||
|
return d
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from version import ELECTRUM_VERSION, SEED_VERSION
|
from version import ELECTRUM_VERSION, SEED_VERSION
|
||||||
|
|
||||||
|
@ -60,7 +81,6 @@ class Wallet:
|
||||||
self.use_change = config.get('use_change',True)
|
self.use_change = config.get('use_change',True)
|
||||||
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.master_public_key = config.get('master_public_key','')
|
|
||||||
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.addresses = config.get('addresses', []) # receiving addresses visible for user
|
||||||
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
||||||
|
@ -76,6 +96,9 @@ class Wallet:
|
||||||
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',{})
|
||||||
|
|
||||||
|
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',{})
|
||||||
try:
|
try:
|
||||||
|
@ -122,19 +145,15 @@ 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, sec, password):
|
def import_key(self, sec, password):
|
||||||
# try password
|
# check password
|
||||||
try:
|
|
||||||
seed = self.decode_seed(password)
|
seed = self.decode_seed(password)
|
||||||
except:
|
|
||||||
raise BaseException("Invalid password")
|
|
||||||
|
|
||||||
address = address_from_private_key(sec)
|
address = address_from_private_key(sec)
|
||||||
|
|
||||||
if address in self.all_addresses():
|
if address in self.all_addresses():
|
||||||
raise BaseException('Address already in wallet')
|
raise BaseException('Address already in wallet')
|
||||||
|
|
||||||
# store the originally requested keypair into the imported keys table
|
# store the originally requested keypair into the imported keys table
|
||||||
self.imported_keys[address] = self.pw_encode(sec, password )
|
self.imported_keys[address] = pw_encode(sec, password )
|
||||||
return address
|
return address
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,11 +168,8 @@ class Wallet:
|
||||||
|
|
||||||
def init_mpk(self,seed):
|
def init_mpk(self,seed):
|
||||||
# public key
|
# public key
|
||||||
curve = SECP256k1
|
self.sequence = DeterministicSequence.from_seed(seed)
|
||||||
secexp = self.stretch_key(seed)
|
self.config.set_key('master_public_key', self.sequence.master_public_key, True)
|
||||||
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
|
||||||
self.master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
|
|
||||||
self.config.set_key('master_public_key', self.master_public_key, True)
|
|
||||||
|
|
||||||
def all_addresses(self):
|
def all_addresses(self):
|
||||||
return self.addresses + self.change_addresses + self.imported_keys.keys()
|
return self.addresses + self.change_addresses + self.imported_keys.keys()
|
||||||
|
@ -173,23 +189,35 @@ class Wallet:
|
||||||
return False
|
return False
|
||||||
return addr == hash_160_to_bc_address(h, addrtype)
|
return addr == hash_160_to_bc_address(h, addrtype)
|
||||||
|
|
||||||
def stretch_key(self,seed):
|
def get_master_public_key(self):
|
||||||
oldseed = seed
|
return self.sequence.master_public_key
|
||||||
for i in range(100000):
|
|
||||||
seed = hashlib.sha256(seed + oldseed).digest()
|
|
||||||
return string_to_number( seed )
|
|
||||||
|
|
||||||
def get_sequence(self,n,for_change):
|
def get_public_key(self, address):
|
||||||
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
|
if address in self.imported_keys.keys():
|
||||||
|
raise BaseException("imported key")
|
||||||
|
|
||||||
|
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 self.sequence.get_pubkey(n, for_change)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_seed(self, password):
|
||||||
|
seed = pw_decode(self.seed, password)
|
||||||
|
self.sequence.check_seed(seed)
|
||||||
|
return seed
|
||||||
|
|
||||||
def get_private_key(self, address, password):
|
def get_private_key(self, address, password):
|
||||||
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
|
||||||
|
|
||||||
# decode seed in any case, in order to make test the password
|
# decode seed in any case, in order to test the password
|
||||||
seed = self.decode_seed(password)
|
seed = self.decode_seed(password)
|
||||||
|
|
||||||
if address in self.imported_keys.keys():
|
if address in self.imported_keys.keys():
|
||||||
return self.pw_decode( self.imported_keys[address], password )
|
return pw_decode( self.imported_keys[address], password )
|
||||||
else:
|
else:
|
||||||
if address in self.addresses:
|
if address in self.addresses:
|
||||||
n = self.addresses.index(address)
|
n = self.addresses.index(address)
|
||||||
|
@ -200,12 +228,7 @@ class Wallet:
|
||||||
else:
|
else:
|
||||||
raise BaseException("unknown address", address)
|
raise BaseException("unknown address", address)
|
||||||
|
|
||||||
order = generator_secp256k1.order()
|
return self.sequence.get_private_key(n, for_change, seed)
|
||||||
secexp = self.stretch_key(seed)
|
|
||||||
secexp = ( secexp + self.get_sequence(n,for_change) ) % order
|
|
||||||
pk = number_to_string( secexp, generator_secp256k1.order() )
|
|
||||||
compressed = False
|
|
||||||
return SecretToASecret( pk, compressed )
|
|
||||||
|
|
||||||
|
|
||||||
def sign_message(self, address, message, password):
|
def sign_message(self, address, message, password):
|
||||||
|
@ -225,17 +248,11 @@ class Wallet:
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def get_new_address(self, n, for_change):
|
def get_new_address(self, n, for_change):
|
||||||
""" Publickey(type,n) = Master_public_key + H(n|S|type)*point """
|
pubkey = self.sequence.get_pubkey(n, for_change)
|
||||||
curve = SECP256k1
|
address = public_key_to_bc_address( pubkey.decode('hex') )
|
||||||
z = self.get_sequence(n, for_change)
|
print_msg( address )
|
||||||
master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.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 )
|
|
||||||
address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() )
|
|
||||||
print address
|
|
||||||
return address
|
return address
|
||||||
|
|
||||||
|
|
||||||
def change_gap_limit(self, value):
|
def change_gap_limit(self, value):
|
||||||
if value >= self.gap_limit:
|
if value >= self.gap_limit:
|
||||||
self.gap_limit = value
|
self.gap_limit = value
|
||||||
|
@ -303,7 +320,7 @@ class Wallet:
|
||||||
|
|
||||||
|
|
||||||
def synchronize(self):
|
def synchronize(self):
|
||||||
if not self.master_public_key:
|
if not self.sequence.master_public_key:
|
||||||
return []
|
return []
|
||||||
new_addresses = []
|
new_addresses = []
|
||||||
new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False)
|
new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False)
|
||||||
|
@ -341,7 +358,7 @@ class Wallet:
|
||||||
import datetime
|
import datetime
|
||||||
if not tx_hash: return ''
|
if not tx_hash: return ''
|
||||||
tx = self.transactions.get(tx_hash)
|
tx = self.transactions.get(tx_hash)
|
||||||
is_mine, v, fee = self.get_tx_value(tx_hash)
|
is_mine, v, fee = self.get_tx_value(tx)
|
||||||
conf, timestamp = self.verifier.get_confirmations(tx_hash)
|
conf, timestamp = self.verifier.get_confirmations(tx_hash)
|
||||||
|
|
||||||
if timestamp:
|
if timestamp:
|
||||||
|
@ -349,8 +366,8 @@ class Wallet:
|
||||||
else:
|
else:
|
||||||
time_str = 'pending'
|
time_str = 'pending'
|
||||||
|
|
||||||
inputs = map(lambda x: x.get('address'), tx['inputs'])
|
inputs = map(lambda x: x.get('address'), tx.inputs)
|
||||||
outputs = map(lambda x: x.get('address'), tx['outputs'])
|
outputs = map(lambda x: x.get('address'), tx.d['outputs'])
|
||||||
tx_details = "Transaction Details" +"\n\n" \
|
tx_details = "Transaction Details" +"\n\n" \
|
||||||
+ "Transaction ID:\n" + tx_hash + "\n\n" \
|
+ "Transaction ID:\n" + tx_hash + "\n\n" \
|
||||||
+ "Status: %d confirmations\n"%conf
|
+ "Status: %d confirmations\n"%conf
|
||||||
|
@ -448,6 +465,24 @@ class Wallet:
|
||||||
return conf, unconf
|
return conf, unconf
|
||||||
|
|
||||||
|
|
||||||
|
def get_unspent_coins(self, domain=None):
|
||||||
|
coins = []
|
||||||
|
if domain is None: domain = self.all_addresses()
|
||||||
|
for addr in domain:
|
||||||
|
h = self.history.get(addr, [])
|
||||||
|
if h == ['*']: continue
|
||||||
|
for tx_hash, tx_height in h:
|
||||||
|
tx = self.transactions.get(tx_hash)
|
||||||
|
for output in tx.d.get('outputs'):
|
||||||
|
if output.get('address') != addr: continue
|
||||||
|
key = tx_hash + ":%d" % output.get('index')
|
||||||
|
if key in self.spent_outputs: continue
|
||||||
|
output['tx_hash'] = tx_hash
|
||||||
|
coins.append(output)
|
||||||
|
return coins
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
|
def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
|
||||||
""" todo: minimize tx size """
|
""" todo: minimize tx size """
|
||||||
total = 0
|
total = 0
|
||||||
|
@ -462,31 +497,8 @@ class Wallet:
|
||||||
for i in self.prioritized_addresses:
|
for i in self.prioritized_addresses:
|
||||||
if i in domain: domain.remove(i)
|
if i in domain: domain.remove(i)
|
||||||
|
|
||||||
for addr in domain:
|
coins = self.get_unspent_coins(domain)
|
||||||
h = self.history.get(addr, [])
|
prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
|
||||||
if h == ['*']: continue
|
|
||||||
for tx_hash, tx_height in h:
|
|
||||||
tx = self.transactions.get(tx_hash)
|
|
||||||
for output in tx.get('outputs'):
|
|
||||||
if output.get('address') != addr: continue
|
|
||||||
key = tx_hash + ":%d" % output.get('index')
|
|
||||||
if key in self.spent_outputs: continue
|
|
||||||
output['tx_hash'] = tx_hash
|
|
||||||
coins.append(output)
|
|
||||||
|
|
||||||
|
|
||||||
for addr in self.prioritized_addresses:
|
|
||||||
h = self.history.get(addr, [])
|
|
||||||
if h == ['*']: continue
|
|
||||||
for tx_hash, tx_height in h:
|
|
||||||
tx = self.transactions.get(tx_hash)
|
|
||||||
for output in tx.get('outputs'):
|
|
||||||
if output.get('address') != addr: continue
|
|
||||||
key = tx_hash + ":%d" % output.get('index')
|
|
||||||
if key in self.spent_outputs: continue
|
|
||||||
output['tx_hash'] = tx_hash
|
|
||||||
prioritized_coins.append(output)
|
|
||||||
|
|
||||||
|
|
||||||
inputs = []
|
inputs = []
|
||||||
coins = prioritized_coins + coins
|
coins = prioritized_coins + coins
|
||||||
|
@ -516,39 +528,6 @@ class Wallet:
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
try:
|
|
||||||
d = DecodeAES(secret, s)
|
|
||||||
except:
|
|
||||||
raise BaseException('Invalid password')
|
|
||||||
return d
|
|
||||||
else:
|
|
||||||
return s
|
|
||||||
|
|
||||||
def decode_seed(self, password):
|
|
||||||
seed = self.pw_decode(self.seed, password)
|
|
||||||
|
|
||||||
# check decoded seed with master public key
|
|
||||||
curve = SECP256k1
|
|
||||||
secexp = self.stretch_key(seed)
|
|
||||||
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
|
||||||
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
|
|
||||||
if master_public_key != self.master_public_key:
|
|
||||||
print_error('invalid password (mpk)')
|
|
||||||
raise BaseException('Invalid password')
|
|
||||||
|
|
||||||
return seed
|
|
||||||
|
|
||||||
|
|
||||||
def get_history(self, address):
|
def get_history(self, address):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return self.history.get(address)
|
return self.history.get(address)
|
||||||
|
@ -605,7 +584,7 @@ class Wallet:
|
||||||
def get_tx_history(self):
|
def get_tx_history(self):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
history = self.transactions.items()
|
history = self.transactions.items()
|
||||||
history.sort(key = lambda x: self.tx_height.get(x[0],1e12) )
|
history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12)
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
balance = 0
|
balance = 0
|
||||||
|
@ -629,16 +608,6 @@ class Wallet:
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_transactions_at_height(self, height):
|
|
||||||
with self.lock:
|
|
||||||
values = self.transactions.values()[:]
|
|
||||||
|
|
||||||
out = []
|
|
||||||
for tx in values:
|
|
||||||
if tx['height'] == height:
|
|
||||||
out.append(tx['tx_hash'])
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def get_label(self, tx_hash):
|
def get_label(self, tx_hash):
|
||||||
label = self.labels.get(tx_hash)
|
label = self.labels.get(tx_hash)
|
||||||
|
@ -646,6 +615,7 @@ class Wallet:
|
||||||
if is_default: label = self.get_default_label(tx_hash)
|
if is_default: label = self.get_default_label(tx_hash)
|
||||||
return label, is_default
|
return label, is_default
|
||||||
|
|
||||||
|
|
||||||
def get_default_label(self, tx_hash):
|
def get_default_label(self, tx_hash):
|
||||||
tx = self.transactions.get(tx_hash)
|
tx = self.transactions.get(tx_hash)
|
||||||
default_label = ''
|
default_label = ''
|
||||||
|
@ -791,12 +761,12 @@ class Wallet:
|
||||||
def update_password(self, seed, old_password, new_password):
|
def update_password(self, seed, old_password, new_password):
|
||||||
if new_password == '': new_password = None
|
if new_password == '': new_password = None
|
||||||
self.use_encryption = (new_password != None)
|
self.use_encryption = (new_password != None)
|
||||||
self.seed = self.pw_encode( seed, new_password)
|
self.seed = pw_encode( seed, new_password)
|
||||||
self.config.set_key('seed', self.seed, True)
|
self.config.set_key('seed', self.seed, True)
|
||||||
for k in self.imported_keys.keys():
|
for k in self.imported_keys.keys():
|
||||||
a = self.imported_keys[k]
|
a = self.imported_keys[k]
|
||||||
b = self.pw_decode(a, old_password)
|
b = pw_decode(a, old_password)
|
||||||
c = self.pw_encode(b, new_password)
|
c = pw_encode(b, new_password)
|
||||||
self.imported_keys[k] = c
|
self.imported_keys[k] = c
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue