3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 import sys, base64, os, re, hashlib, socket, getpass, copy, operator, ast
25 print "python-ecdsa does not seem to be installed. Try 'sudo easy_install ecdsa'"
32 has_encryption = False
35 ############ functions from pywallet #####################
39 def hash_160(public_key):
40 md = hashlib.new('ripemd160')
41 md.update(hashlib.sha256(public_key).digest())
44 def public_key_to_bc_address(public_key):
45 h160 = hash_160(public_key)
46 return hash_160_to_bc_address(h160)
48 def hash_160_to_bc_address(h160):
49 vh160 = chr(addrtype) + h160
52 return b58encode(addr)
54 def bc_address_to_hash_160(addr):
55 bytes = b58decode(addr, 25)
58 __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
59 __b58base = len(__b58chars)
62 """ encode v, which is a string of bytes, to base58.
66 for (i, c) in enumerate(v[::-1]):
67 long_value += (256**i) * ord(c)
70 while long_value >= __b58base:
71 div, mod = divmod(long_value, __b58base)
72 result = __b58chars[mod] + result
74 result = __b58chars[long_value] + result
76 # Bitcoin does a little leading-zero-compression:
77 # leading 0-bytes in the input become leading-1s
80 if c == '\0': nPad += 1
83 return (__b58chars[0]*nPad) + result
85 def b58decode(v, length):
86 """ decode v into a string of len bytes
89 for (i, c) in enumerate(v[::-1]):
90 long_value += __b58chars.find(c) * (__b58base**i)
93 while long_value >= 256:
94 div, mod = divmod(long_value, 256)
95 result = chr(mod) + result
97 result = chr(long_value) + result
101 if c == __b58chars[0]: nPad += 1
104 result = chr(0)*nPad + result
105 if length is not None and len(result) != length:
112 return hashlib.sha256(hashlib.sha256(data).digest()).digest()
114 def EncodeBase58Check(vchIn):
116 return b58encode(vchIn + hash[0:4])
118 def DecodeBase58Check(psz):
119 vchRet = b58decode(psz, None)
129 def PrivKeyToSecret(privkey):
130 return privkey[9:9+32]
132 def SecretToASecret(secret):
133 vchIn = chr(addrtype+128) + secret
134 return EncodeBase58Check(vchIn)
136 def ASecretToSecret(key):
137 vch = DecodeBase58Check(key)
138 if vch and vch[0] == chr(addrtype+128):
143 ########### end pywallet functions #######################
146 def int_to_hex(i, length=1):
147 s = hex(i)[2:].rstrip('L')
148 s = "0"*(2*length - len(s)) + s
149 return s.decode('hex')[::-1].encode('hex')
152 # password encryption
153 from Crypto.Cipher import AES
156 pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
157 EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
158 DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
161 # secp256k1, http://www.oid-info.com/get/1.3.132.0.10
162 _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
163 _r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
164 _b = 0x0000000000000000000000000000000000000000000000000000000000000007L
165 _a = 0x0000000000000000000000000000000000000000000000000000000000000000L
166 _Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
167 _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
168 curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b )
169 generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
170 oid_secp256k1 = (1,3,132,0,10)
171 SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 )
175 out = re.sub('( [^\n]*|)\n','',s)
176 out = out.replace(' ','')
177 out = out.replace('\n','')
180 def raw_tx( inputs, outputs, for_sig = None ):
181 s = int_to_hex(1,4) + ' version\n'
182 s += int_to_hex( len(inputs) ) + ' number of inputs\n'
183 for i in range(len(inputs)):
184 _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i]
185 s += p_hash.decode('hex')[::-1].encode('hex') + ' prev hash\n'
186 s += int_to_hex(p_index,4) + ' prev index\n'
188 sig = sig + chr(1) # hashtype
189 script = int_to_hex( len(sig)) + ' push %d bytes\n'%len(sig)
190 script += sig.encode('hex') + ' sig\n'
191 pubkey = chr(4) + pubkey
192 script += int_to_hex( len(pubkey)) + ' push %d bytes\n'%len(pubkey)
193 script += pubkey.encode('hex') + ' pubkey\n'
195 script = p_script + ' scriptsig \n'
198 s += int_to_hex( len(filter(script))/2 ) + ' script length \n'
200 s += "ffffffff" + ' sequence\n'
201 s += int_to_hex( len(outputs) ) + ' number of outputs\n'
202 for output in outputs:
203 addr, amount = output
204 s += int_to_hex( amount, 8) + ' amount: %d\n'%amount
205 script = '76a9' # op_dup, op_hash_160
206 script += '14' # push 0x14 bytes
207 script += bc_address_to_hash_160(addr).encode('hex')
208 script += '88ac' # op_equalverify, op_checksig
209 s += int_to_hex( len(filter(script))/2 ) + ' script length \n'
210 s += script + ' script \n'
211 s += int_to_hex(0,4) # lock time
212 if for_sig is not None: s += int_to_hex(1, 4) # hash type
215 class InvalidPassword(Exception):
221 def __init__(self, wallet_dir):
223 self.gap_limit = 5 # configuration
224 self.host = 'ecdsa.org'
230 self.use_encryption = False
232 self.seed = '' # encrypted
233 self.private_keys = repr([]) # encrypted
234 self.change_addresses = [] # index of addresses used as change
235 self.status = {} # current status of addresses
237 self.labels = {} # labels for addresses and transactions
238 self.addressbook = [] # outgoing addresses, for payments
246 self.init_path(wallet_dir)
249 def init_path(self, wallet_dir):
250 if wallet_dir is None:
251 if "HOME" in os.environ:
252 wallet_dir = os.path.join( os.environ["HOME"], '.electrum')
253 elif "LOCALAPPDATA" in os.environ:
254 wallet_dir = os.path.join( os.environ["LOCALAPPDATA"], 'Electrum' )
255 elif "APPDATA" in os.environ:
256 wallet_dir = os.path.join( os.environ["APPDATA"], 'Electrum' )
258 print "No home directory found in environment variables."
261 if not os.path.exists( wallet_dir ):
262 os.mkdir( wallet_dir )
264 self.path = os.path.join( wallet_dir, 'electrum.dat')
266 def new_seed(self, password):
267 seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
268 self.seed = wallet.pw_encode( seed, password)
270 def is_mine(self, address):
271 return address in self.addresses
273 def is_change(self, address):
274 if not self.is_mine(address):
276 k = self.addresses.index(address)
277 return k in self.change_addresses
279 def is_valid(self,addr):
280 ADDRESS_RE = re.compile('[1-9A-HJ-NP-Za-km-z]{26,}\\Z')
281 if not ADDRESS_RE.match(addr): return False
282 h = bc_address_to_hash_160(addr)
283 return addr == hash_160_to_bc_address(h)
285 def create_new_address(self, for_change, password):
286 seed = self.pw_decode( self.seed, password)
288 for i in range(100000):
290 seed = hashlib.sha512(seed + oldseed).digest()
291 i = len( self.addresses ) - len(self.change_addresses) if not for_change else len(self.change_addresses)
292 seed = Hash( "%d:%d:"%(i,for_change) + seed )
293 order = generator_secp256k1.order()
294 secexp = ecdsa.util.randrange_from_seed__trytryagain( seed, order )
295 secret = SecretToASecret( ('%064x' % secexp).decode('hex') )
296 private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
297 public_key = private_key.get_verifying_key()
298 address = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() )
300 private_keys = ast.literal_eval( self.pw_decode( self.private_keys, password) )
301 private_keys.append(secret)
303 raise InvalidPassword("")
304 self.private_keys = self.pw_encode( repr(private_keys), password)
305 self.addresses.append(address)
306 if for_change: self.change_addresses.append( len(self.addresses) - 1 )
307 self.history[address] = []
308 self.status[address] = None
313 def recover(self, password):
314 seed = self.pw_decode( self.seed, password)
315 # todo: recover receiving addresses from tx
318 addr = self.create_new_address(True, password)
319 self.history[addr] = h = self.retrieve_history(addr)
320 self.status[addr] = h[-1]['blk_hash'] if h else None
321 #print "recovering", addr
322 if self.status[addr] is not None:
329 addr = self.create_new_address(False, password)
330 self.history[addr] = h = self.retrieve_history(addr)
331 self.status[addr] = h[-1]['blk_hash'] if h else None
332 #print "recovering", addr
333 if self.status[addr] is None:
335 if num_gap == self.gap_limit: break
340 if not is_found: return False
342 # remove limit-1 addresses. [ this is ok, because change addresses are at the beginning of the list]
344 self.addresses = self.addresses[:-n]
345 private_keys = ast.literal_eval( self.pw_decode( self.private_keys, password))
346 private_keys = private_keys[:-n]
347 self.private_keys = self.pw_encode( repr(private_keys), password)
349 # history and addressbook
350 self.update_tx_history()
351 for tx in self.tx_history.values():
353 for i in tx['outputs']:
354 if not self.is_mine(i) and i not in self.addressbook:
355 self.addressbook.append(i)
357 self.update_tx_labels()
361 s = repr( (self.version, self.use_encryption, self.fee, self.host, self.port, self.blocks,
362 self.seed, self.addresses, self.private_keys,
363 self.change_addresses, self.status, self.history,
364 self.labels, self.addressbook) )
365 f = open(self.path,"w")
371 f = open(self.path,"r")
377 sequence = ast.literal_eval( data )
378 (self.version, self.use_encryption, self.fee, self.host, self.port, self.blocks,
379 self.seed, self.addresses, self.private_keys,
380 self.change_addresses, self.status, self.history,
381 self.labels, self.addressbook) = sequence
383 raise BaseException("version error.")
384 self.update_tx_history()
387 def get_new_address(self, password):
389 for addr in self.addresses[-self.gap_limit:]:
390 if self.history[addr] == []:
392 if n < self.gap_limit:
394 new_address = self.create_new_address(False, password)
395 except InvalidPassword:
396 return False, "wrong password"
398 return True, new_address
400 return False, "The last %d addresses in your list have never been used. You should use them first, or increase the allowed gap size in your preferences. "%self.gap_limit
402 def get_addr_balance(self, addr):
403 h = self.history.get(addr)
413 def get_balance(self):
415 for addr in self.addresses:
416 c, u = self.get_addr_balance(addr)
422 return self.port in [80,8080,443]
424 def request(self, request ):
429 import httplib, urllib
430 params = urllib.urlencode({'q':request})
431 headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
432 conn = httplib.HTTPConnection(self.host)
433 conn.request("POST", "/electrum.php", params, headers)
434 response = conn.getresponse()
435 if response.status == 200:
436 out = response.read()
441 s = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
442 s.connect(( self.host, self.port))
451 self.rtime = time.time() - t1
454 def send_tx(self, data):
455 return self.request( repr ( ('tx', data )))
457 def retrieve_history(self, address):
458 return ast.literal_eval( self.request( repr ( ('h', address ))) )
461 return ast.literal_eval( self.request( repr ( ('poll', self.session_id ))))
463 def new_session(self):
464 self.session_id, self.message = ast.literal_eval( self.request( repr ( ('session', repr(self.addresses) ))))
467 blocks, changed_addresses = self.poll()
468 if blocks == -1: raise BaseException("session not found")
469 self.blocks = int(blocks)
470 for addr, blk_hash in changed_addresses.items():
471 if self.status[addr] != blk_hash:
472 print "updating history for", addr
473 self.history[addr] = self.retrieve_history(addr)
474 self.status[addr] = blk_hash
475 self.update_tx_history()
476 if changed_addresses:
481 def choose_inputs_outputs( self, to_addr, amount, fee, password):
482 """ todo: minimize tx size """
484 amount = int( 1e8*amount )
488 for addr in self.addresses:
489 h = self.history.get(addr)
491 if item.get('raw_scriptPubKey'):
492 v = item.get('value')
494 inputs.append((addr, v, item['tx_hash'], item['pos'], item['raw_scriptPubKey'], None, None) )
495 if total >= amount + fee: break
496 if total >= amount + fee: break
498 print "not enough funds: %d %d"%(total, fee)
499 return False, "not enough funds: %d %d"%(total, fee)
500 outputs = [ (to_addr, amount) ]
501 change_amount = total - ( amount + fee )
502 if change_amount != 0:
503 # first look for unused change addresses
504 for addr in self.addresses:
505 i = self.addresses.index(addr)
506 if i not in self.change_addresses: continue
507 if self.history.get(addr): continue
508 change_address = addr
511 change_address = self.create_new_address(True, password)
512 print "new change address", change_address
513 outputs.append( (change_address, change_amount) )
514 return inputs, outputs
516 def sign_inputs( self, inputs, outputs, password ):
518 for i in range(len(inputs)):
519 addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
520 private_key = self.get_private_key(addr, password)
521 public_key = private_key.get_verifying_key()
522 pubkey = public_key.to_string()
523 tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
524 sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
525 assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
526 s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
529 def pw_encode(self, s, password):
531 secret = Hash(password)
532 cipher = AES.new(secret)
533 return EncodeAES(cipher, s)
537 def pw_decode(self, s, password):
539 secret = Hash(password)
540 cipher = AES.new(secret)
541 return DecodeAES(cipher, s)
545 def get_private_key( self, addr, password ):
547 private_keys = ast.literal_eval( self.pw_decode( self.private_keys, password ) )
549 raise InvalidPassword("")
550 k = self.addresses.index(addr)
551 secret = private_keys[k]
552 b = ASecretToSecret(secret)
553 secexp = int( b.encode('hex'), 16)
554 private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 )
555 public_key = private_key.get_verifying_key()
556 assert addr == public_key_to_bc_address( chr(4) + public_key.to_string() )
559 def get_tx_history(self):
560 lines = self.tx_history.values()
561 lines = sorted(lines, key=operator.itemgetter("nTime"))
564 def update_tx_history(self):
566 for addr in self.addresses:
567 for tx in self.history[addr]:
568 tx_hash = tx['tx_hash']
569 line = self.tx_history.get(tx_hash)
571 self.tx_history[tx_hash] = copy.copy(tx)
572 line = self.tx_history.get(tx_hash)
574 line['value'] += tx['value']
575 if line['blk_hash'] == 'mempool':
577 self.update_tx_labels()
579 def update_tx_labels(self):
580 for tx in self.tx_history.values():
583 for o_addr in tx['outputs']:
584 if not self.is_change(o_addr):
585 dest_label = self.labels.get(o_addr)
587 default_label = 'to: ' + dest_label
589 default_label = 'to: ' + o_addr
591 for o_addr in tx['outputs']:
592 if self.is_mine(o_addr) and not self.is_change(o_addr):
593 dest_label = self.labels.get(o_addr)
595 default_label = 'at: ' + dest_label
597 default_label = 'at: ' + o_addr
598 tx['default_label'] = default_label
600 def mktx(self, to_address, amount, label, password, fee=None):
601 if not self.is_valid(to_address):
602 return False, "Invalid address"
603 if fee is None: fee = self.fee
605 inputs, outputs = wallet.choose_inputs_outputs( to_address, amount, fee, password )
607 return False, "Not enough funds"
608 s_inputs = wallet.sign_inputs( inputs, outputs, password )
609 except InvalidPassword:
610 return False, "Wrong password"
611 tx = filter( raw_tx( s_inputs, outputs ) )
612 if to_address not in self.addressbook:
613 self.addressbook.append(to_address)
615 tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
616 wallet.labels[tx_hash] = label
620 def sendtx(self, tx):
621 tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
622 out = self.send_tx(tx)
624 return False, "error: " + out
629 from optparse import OptionParser
631 if __name__ == '__main__':
632 known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'payto', 'sendtx', 'password', 'newaddress', 'addresses', 'history', 'label', 'gui', 'mktx','seed']
634 usage = "usage: %prog [options] command args\nCommands: "+ (', '.join(known_commands))
636 parser = OptionParser(usage=usage)
637 parser.add_option("-d", "--dir", dest="wallet_dir", help="wallet directory")
638 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
639 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
640 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
641 parser.add_option("-f", "--fee", dest="tx_fee", default=0.005, help="set tx fee")
642 options, args = parser.parse_args()
652 if cmd not in known_commands:
655 wallet = Wallet(options.wallet_dir)
659 gui.init_wallet(wallet)
660 gui = gui.BitcoinGUI(wallet)
665 if not wallet.read() and cmd != 'help':
666 print "Wallet file not found."
667 print "Type 'electrum.py create' to create a new wallet, or provide a path to a wallet with the -d option"
672 print "remove the existing wallet first!"
675 password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
677 password2 = getpass.getpass("Confirm password:")
678 if password != password2:
683 print "in order to use wallet encryption, please install pycrypto (sudo easy_install pycrypto)"
685 host = raw_input("server (default:ecdsa.org):")
686 port = raw_input("port (default:50000):")
687 fee = raw_input("fee (default 0.005):")
688 if fee: wallet.fee = float(fee)
689 if host: wallet.host = host
690 if port: wallet.port = int(port)
691 seed = raw_input("if you are restoring an existing wallet, enter the seed. otherwise just press enter: ")
695 gap = raw_input("gap limit (default 5):")
696 if gap: wallet.gap_limit = int(gap)
697 print "recovering wallet..."
698 r = wallet.recover(password)
700 print "recovery successful"
703 print "no wallet found"
705 wallet.new_seed(None)
706 print "Your seed is", wallet.seed
707 print "Please store it safely"
709 wallet.create_new_address(False, None)
712 if cmd in ['payto', 'mktx']:
715 amount = float(args[2])
716 label = ' '.join(args[3:])
717 if options.tx_fee: options.tx_fee = float(options.tx_fee)
723 if cmd not in ['password', 'mktx', 'history', 'label','contacts','help','validateaddress']:
728 # commands needing password
729 if cmd in ['payto', 'password', 'newaddress','mktx','seed'] or ( cmd=='addresses' and options.show_keys):
730 password = getpass.getpass('Password:') if wallet.use_encryption else None
734 if cmd2 not in known_commands:
735 print "known commands:", ', '.join(known_commands)
736 print "help <command> shows the help on a specific command"
737 elif cmd2 == 'balance':
738 print "display the balance of your wallet"
739 elif cmd2 == 'contacts':
740 print "show your list of contacts"
741 elif cmd2 == 'payto':
742 print "payto <recipient> <amount> [label]"
743 print "create and broadcast a transaction."
744 print "<recipient> can be a bitcoin address or a label"
745 elif cmd2== 'sendtx':
747 print "broadcast a transaction to the network. <tx> must be in hexadecimal"
748 elif cmd2 == 'password':
749 print "change your password"
750 elif cmd2 == 'newaddress':
751 print "create a new receiving address. password is needed."
752 elif cmd2 == 'addresses':
753 print "show your list of addresses. options: -a, -k, -b"
754 elif cmd2 == 'history':
755 print "show the transaction history"
756 elif cmd2 == 'label':
757 print "assign a label to an item"
759 print "start the GUI"
761 print "create a signed transaction. password protected"
762 print "syntax: mktx <recipient> <amount> [label]"
764 print "show generation seed of your wallet. password protected."
768 print wallet.seed, '"'+' '.join(mnemonic.mn_encode(wallet.seed))+'"'
770 elif cmd == 'validateaddress':
772 print wallet.is_valid(addr)
774 elif cmd == 'balance':
775 c, u = wallet.get_balance()
781 elif cmd in [ 'contacts']:
782 for addr in wallet.addressbook:
783 print addr, " ", wallet.labels.get(addr)
785 elif cmd in [ 'addresses']:
786 if options.show_keys: private_keys = ast.literal_eval( wallet.pw_decode( wallet.private_keys, password ) )
787 for addr in wallet.addresses:
788 if options.show_all or not wallet.is_change(addr):
789 label = wallet.labels.get(addr) if not wallet.is_change(addr) else "[change]"
790 if label is None: label = ''
791 if options.show_balance:
792 h = wallet.history.get(addr)
795 if item['is_in']: ni += 1
797 b = "%d %d %f"%(no, ni, wallet.get_addr_balance(addr)[0]*1e-8)
799 pk = private_keys[wallet.addresses.index(addr)] if options.show_keys else ''
800 print addr, pk, b, label
803 lines = wallet.get_tx_history()
807 v = 1.*line['value']/1e8
809 v_str = "%f"%v if v<0 else "+%f"%v
811 time_str = datetime.datetime.fromtimestamp( line['nTime'])
815 label = line.get('label')
816 if not label: label = line['tx_hash']
817 else: label = label + ' '*(64 - len(label) )
819 print time_str, " ", label, " ", v_str, " ", "%f"%b
820 print "# balance: ", b
825 label = ' '.join(args[2:])
827 print "syntax: label <tx_hash> <text>"
829 wallet.labels[tx] = label
832 elif cmd in ['payto', 'mktx']:
833 for k, v in wallet.labels.items():
836 print "alias", to_address
838 r, h = wallet.mktx( to_address, amount, label, password, fee = options.tx_fee )
839 if r and cmd=='payto':
840 r, h = wallet.sendtx( tx )
847 r, h = wallet.sendtx( tx )
850 elif cmd == 'newaddress':
851 a = wallet.get_new_address()
855 print "Maximum gap reached. Increase gap in order to create more addresses."
857 elif cmd == 'password':
859 seed = wallet.pw_decode( wallet.seed, password)
860 private_keys = ast.literal_eval( wallet.pw_decode( wallet.private_keys, password) )
864 new_password = getpass.getpass('New password:')
865 if new_password == getpass.getpass('Confirm new password:'):
866 wallet.use_encryption = (new_password != '')
867 wallet.seed = wallet.pw_encode( seed, new_password)
868 wallet.private_keys = wallet.pw_encode( repr( private_keys ), new_password)
871 print "error: mismatch"