f21585be2c321e7ac2797725655213f5908b6c00
[electrum-nvc.git] / client / electrum.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
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.
10 #
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.
15 #
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/>.
18
19
20 import sys, base64, os, re, hashlib, socket, getpass, copy, operator, urllib2, ast
21
22 try:
23     import ecdsa  
24 except:
25     print "python-ecdsa does not seem to be installed. Try 'sudo easy_install ecdsa'"
26     exit(1)
27
28 try:
29     import Crypto
30     has_encryption = True
31 except:
32     has_encryption = False
33
34
35 ############ functions from pywallet ##################### 
36
37 addrtype = 0
38
39 def hash_160(public_key):
40     md = hashlib.new('ripemd160')
41     md.update(hashlib.sha256(public_key).digest())
42     return md.digest()
43
44 def public_key_to_bc_address(public_key):
45     h160 = hash_160(public_key)
46     return hash_160_to_bc_address(h160)
47
48 def hash_160_to_bc_address(h160):
49     vh160 = chr(addrtype) + h160
50     h = Hash(vh160)
51     addr = vh160 + h[0:4]
52     return b58encode(addr)
53
54 def bc_address_to_hash_160(addr):
55     bytes = b58decode(addr, 25)
56     return bytes[1:21]
57
58 __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
59 __b58base = len(__b58chars)
60
61 def b58encode(v):
62     """ encode v, which is a string of bytes, to base58.                
63     """
64
65     long_value = 0L
66     for (i, c) in enumerate(v[::-1]):
67         long_value += (256**i) * ord(c)
68
69     result = ''
70     while long_value >= __b58base:
71         div, mod = divmod(long_value, __b58base)
72         result = __b58chars[mod] + result
73         long_value = div
74     result = __b58chars[long_value] + result
75
76     # Bitcoin does a little leading-zero-compression:
77     # leading 0-bytes in the input become leading-1s
78     nPad = 0
79     for c in v:
80         if c == '\0': nPad += 1
81         else: break
82
83     return (__b58chars[0]*nPad) + result
84
85 def b58decode(v, length):
86     """ decode v into a string of len bytes
87     """
88     long_value = 0L
89     for (i, c) in enumerate(v[::-1]):
90         long_value += __b58chars.find(c) * (__b58base**i)
91
92     result = ''
93     while long_value >= 256:
94         div, mod = divmod(long_value, 256)
95         result = chr(mod) + result
96         long_value = div
97     result = chr(long_value) + result
98
99     nPad = 0
100     for c in v:
101         if c == __b58chars[0]: nPad += 1
102         else: break
103
104     result = chr(0)*nPad + result
105     if length is not None and len(result) != length:
106         return None
107
108     return result
109
110
111 def Hash(data):
112     return hashlib.sha256(hashlib.sha256(data).digest()).digest()
113
114 def EncodeBase58Check(vchIn):
115     hash = Hash(vchIn)
116     return b58encode(vchIn + hash[0:4])
117
118 def DecodeBase58Check(psz):
119     vchRet = b58decode(psz, None)
120     key = vchRet[0:-4]
121     csum = vchRet[-4:]
122     hash = Hash(key)
123     cs32 = hash[0:4]
124     if cs32 != csum:
125         return None
126     else:
127         return key
128
129 def PrivKeyToSecret(privkey):
130     return privkey[9:9+32]
131
132 def SecretToASecret(secret):
133     vchIn = chr(addrtype+128) + secret
134     return EncodeBase58Check(vchIn)
135
136 def ASecretToSecret(key):
137     vch = DecodeBase58Check(key)
138     if vch and vch[0] == chr(addrtype+128):
139         return vch[1:]
140     else:
141         return False
142
143 ########### end pywallet functions #######################
144
145
146 def int_to_hex(i, length=1):
147     s = hex(i)[2:]
148     s = "0"*(2*length - len(s)) + s
149     return s.decode('hex')[::-1].encode('hex')
150
151
152 # password encryption
153 from Crypto.Cipher import AES
154 BLOCK_SIZE = 32
155 PADDING = '{'
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)
159
160
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 ) 
172
173
174 no_wallet_message = "Wallet file not found.\nPlease provide a seed and a password. The seed will be to generate Bitcoin addresses. It should be long and random, and nobody should be able to guess it. Memorize it, or write it down and keep it in a vault. The password will be used to encrypt your local wallet file. You will need to enter your password everytime you use your wallet. If you lose your password, you can still recover your wallet with the seed."
175
176 def filter(s): 
177     out = re.sub('( [^\n]*|)\n','',s)
178     out = out.replace(' ','')
179     out = out.replace('\n','')
180     return out
181
182 def raw_tx( inputs, outputs, for_sig = None ):
183     s  = int_to_hex(1,4)                                     +   '     version\n' 
184     s += int_to_hex( len(inputs) )                           +   '     number of inputs\n'
185     for i in range(len(inputs)):
186         _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i]
187         s += p_hash.decode('hex')[::-1].encode('hex')        +  '     prev hash\n'
188         s += int_to_hex(p_index,4)                           +  '     prev index\n'
189         if for_sig is None:
190             sig = sig + chr(1)                               # hashtype
191             script  = int_to_hex( len(sig))                  +  '     push %d bytes\n'%len(sig)
192             script += sig.encode('hex')                      +  '     sig\n'
193             pubkey = chr(4) + pubkey
194             script += int_to_hex( len(pubkey))               +  '     push %d bytes\n'%len(pubkey)
195             script += pubkey.encode('hex')                   +  '     pubkey\n'
196         elif for_sig==i:
197             script = p_script                                +  '     scriptsig \n'
198         else:
199             script=''
200         s += int_to_hex( len(filter(script))/2 )             +  '     script length \n'
201         s += script
202         s += "ffffffff"                                      +  '     sequence\n'
203     s += int_to_hex( len(outputs) )                          +  '     number of outputs\n'
204     for output in outputs:
205         addr, amount = output
206         s += int_to_hex( amount, 8)                          +  '     amount: %d\n'%amount 
207         script = '76a9'                                      # op_dup, op_hash_160
208         script += '14'                                       # push 0x14 bytes
209         script += bc_address_to_hash_160(addr).encode('hex')
210         script += '88ac'                                     # op_equalverify, op_checksig
211         s += int_to_hex( len(filter(script))/2 )             +  '     script length \n'
212         s += script                                          +  '     script \n'
213     s += int_to_hex(0,4)                                     # lock time
214     if for_sig is not None: s += int_to_hex(1, 4)            # hash type
215     return s
216
217 class InvalidPassword(Exception):
218     pass
219
220 wallet_dir = os.environ["HOME"] + '/.bitcoin/'
221 if not os.path.exists( wallet_dir ):
222     os.mkdir( wallet_dir ) 
223 wallet_path = wallet_dir + '/electrum.dat'
224
225 class Wallet:
226     def __init__(self):
227         self.gap_limit = 5           # configuration
228         self.host = 'ecdsa.org'
229         self.port = 50000
230         self.fee = 0.005
231
232         # saved fields
233         self.use_encryption = False
234         self.addresses = []
235         self.seed = ''         # encrypted
236         self.private_keys = repr([]) # encrypted
237         self.change_addresses = []   # index of addresses used as change
238         self.status = {}             # current status of addresses
239         self.history = {}
240         self.labels = {}             # labels for addresses and transactions
241         self.addressbook = []        # outgoing addresses, for payments
242         self.blocks = 0 
243
244         # not saved
245         self.message = ''
246         self.tx_history = {}
247
248     def is_mine(self, address):
249         return address in self.addresses
250
251     def is_change(self, address):
252         if not self.is_mine(address): 
253             return False
254         k = self.addresses.index(address)
255         return k in self.change_addresses
256
257     def is_valid(self,addr):
258         ADDRESS_RE = re.compile('[1-9A-HJ-NP-Za-km-z]{26,}\\Z')
259         return ADDRESS_RE.match(addr)
260
261     def create_new_address(self, for_change, password):
262         seed = self.pw_decode( self.seed, password)
263         i = len( self.addresses ) - len(self.change_addresses) if not for_change else len(self.change_addresses)
264         seed = Hash( "%d:%d:"%(i,for_change) + seed )
265         order = generator_secp256k1.order()
266         secexp = ecdsa.util.randrange_from_seed__trytryagain( seed, order )
267         secret = SecretToASecret( ('%064x' % secexp).decode('hex') )
268         private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
269         public_key = private_key.get_verifying_key()
270         address = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() )
271         try:
272             private_keys = ast.literal_eval( self.pw_decode( self.private_keys, password) )
273             private_keys.append(secret)
274         except:
275             raise InvalidPassword("")
276         self.private_keys = self.pw_encode( repr(private_keys), password)
277         self.addresses.append(address)
278         if for_change: self.change_addresses.append( i )
279         h = self.retrieve_history(address)
280         self.history[address] = h
281         self.status[address] = h[-1]['blk_hash'] if h else None
282         return address
283
284     def recover(self, password):
285         seed = self.pw_decode( self.seed, password)
286         # todo: recover receiving addresses from tx
287         num_gap = 0
288         while True:
289             addr = self.create_new_address(True, password)
290             print "recovering", addr
291             if self.status[addr] is None: break
292
293         num_gap = 0
294         while True:
295             addr = self.create_new_address(False, password)
296             print "recovering", addr
297             if self.status[addr] is None:
298                 num_gap += 1
299                 if num_gap == self.gap_limit: break
300             else:
301                 num_gap = 0
302
303         # remove limit-1 addresses. [ this is ok, because change addresses are at the beginning of the list]
304         n = self.gap_limit
305         self.addresses = self.addresses[:-n]
306         private_keys = ast.literal_eval( self.pw_decode( self.private_keys, password))
307         private_keys = private_keys[:-n]
308         self.private_keys = self.pw_encode( repr(private_keys), password)
309
310         # history and addressbook
311         self.update_tx_history()
312         for tx in self.tx_history.values():
313             if tx['value']<0:
314                 for i in tx['outputs']:
315                     if not self.is_mine(i) and i not in self.addressbook:
316                         self.addressbook.append(i)
317         # redo labels
318         self.update_tx_labels()
319
320     def save(self):
321         s = repr( (self.use_encryption, self.fee, self.host, self.blocks,
322                    self.seed, self.addresses, self.private_keys, 
323                    self.change_addresses, self.status, self.history, 
324                    self.labels, self.addressbook) )
325         f = open(wallet_path,"w")
326         f.write(s)
327         f.close()
328
329     def read(self):
330         try:
331             f = open(wallet_path,"r")
332             data = f.read()
333             f.close()
334         except:
335             return False
336         try:
337             (self.use_encryption, self.fee, self.host, self.blocks, 
338              self.seed, self.addresses, self.private_keys, 
339              self.change_addresses, self.status, self.history, 
340              self.labels, self.addressbook) = ast.literal_eval( data )
341         except:
342             return False
343         self.update_tx_history()
344         return True
345         
346     def get_new_address(self, password):
347         n = 0 
348         for addr in self.addresses[-self.gap_limit:]:
349             if self.history[addr] == []: 
350                 n = n + 1
351         if n < self.gap_limit:
352             try:
353                 new_address = self.create_new_address(False, password)
354             except InvalidPassword:
355                 return False, "wrong password"
356             self.save()
357             return True, new_address
358         else:
359             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
360
361     def get_addr_balance(self, addr):
362         h = self.history.get(addr)
363         c = u = 0
364         for item in h:
365             v = item['value']
366             if item['height']:
367                 c += v
368             else:
369                 u += v
370         return c, u
371
372     def get_balance(self):
373         conf = unconf = 0
374         for addr in self.addresses: 
375             c, u = self.get_addr_balance(addr)
376             conf += c
377             unconf += u
378         return conf, unconf
379
380     def request(self, request ):
381
382         if self.port == 80:
383             try:
384                 out = urllib2.urlopen('http://'+self.host+'/q/tw', request, timeout=5).read()
385             except:
386                 out = ''
387         else:
388             s = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
389             s.connect(( self.host, self.port))
390             s.send(request)
391             out = ''
392             while 1:
393                 msg = s.recv(1024)
394                 if msg: out += msg
395                 else: break
396             s.close()
397
398         if re.match('[^:]\s*\(', out): out = ''
399         return out
400
401     def retrieve_message(self):
402         if not self.message:
403             self.message = self.request( repr ( ('msg', '')))
404
405     def send_tx(self, data):
406         return self.request( repr ( ('tx', data )))
407
408     def retrieve_history(self, address):
409         return ast.literal_eval( self.request( repr ( ('h', address ))) )
410
411     def poll(self):
412         return ast.literal_eval( self.request( repr ( ('poll', '' ))))
413
414     def new_session(self):
415         self.message = self.request( repr ( ('watch', repr(self.addresses) )))
416         
417     def update(self):
418         blocks, changed_addresses = self.poll()
419         self.blocks = blocks
420         for addr, blk_hash in changed_addresses.items():
421             if self.status[addr] != blk_hash:
422                 print "updating history for", addr
423                 self.history[addr] = self.retrieve_history(addr)
424                 self.status[addr] = blk_hash
425         self.update_tx_history()
426         if changed_addresses:
427             return True
428         else:
429             return False
430
431     def choose_inputs_outputs( self, to_addr, amount, fee, password):
432         """ todo: minimize tx size """
433
434         amount = int( 1e8*amount )
435         fee = int( 1e8*fee )
436         total = 0 
437         inputs = []
438         for addr in self.addresses:
439             h = self.history.get(addr)
440             for item in h:
441                 if item.get('raw_scriptPubKey'):
442                     v = item.get('value')
443                     total += v
444                     inputs.append((addr, v, item['tx_hash'], item['pos'], item['raw_scriptPubKey'], None, None) )
445                     if total >= amount + fee: break
446             if total >= amount + fee: break
447         else:
448             print "not enough funds: %d %d"%(total, fee)
449             return False, "not enough funds: %d %d"%(total, fee)
450         outputs = [ (to_addr, amount) ]
451         change_amount = total - ( amount + fee )
452         if change_amount != 0:
453             # first look for unused change addresses 
454             for addr in self.addresses:
455                 i = self.addresses.index(addr)
456                 if i not in self.change_addresses: continue
457                 if self.history.get(addr): continue
458                 change_address = addr
459                 break
460             else:
461                 change_address = self.create_new_address(True, password)
462                 print "new change address", change_address
463             outputs.append( (change_address,  change_amount) )
464         return inputs, outputs
465
466     def sign_inputs( self, inputs, outputs, password ):
467         s_inputs = []
468         for i in range(len(inputs)):
469             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
470             private_key = self.get_private_key(addr, password)
471             public_key = private_key.get_verifying_key()
472             pubkey = public_key.to_string()
473             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
474             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
475             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
476             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
477         return s_inputs
478
479     def pw_encode(self, s, password):
480         if password:
481             secret = Hash(password)
482             cipher = AES.new(secret)
483             return EncodeAES(cipher, s)
484         else:
485             return s
486
487     def pw_decode(self, s, password):
488         if password:
489             secret = Hash(password)
490             cipher = AES.new(secret)
491             return DecodeAES(cipher, s)
492         else:
493             return s
494
495     def get_private_key( self, addr, password ):
496         try:
497             private_keys = ast.literal_eval( self.pw_decode( self.private_keys, password ) )
498         except:
499             raise InvalidPassword("")
500         k = self.addresses.index(addr)
501         secret = private_keys[k]
502         b = ASecretToSecret(secret)
503         secexp = int( b.encode('hex'), 16)
504         private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 )
505         public_key = private_key.get_verifying_key()
506         assert addr == public_key_to_bc_address( chr(4) + public_key.to_string() )
507         return private_key
508
509     def get_tx_history(self):
510         lines = self.tx_history.values()
511         lines = sorted(lines, key=operator.itemgetter("nTime"))
512         return lines
513
514     def update_tx_history(self):
515         self.tx_history= {}
516         for addr in self.addresses:
517             for tx in self.history[addr]:
518                 tx_hash = tx['tx_hash']
519                 line = self.tx_history.get(tx_hash)
520                 if not line:
521                     self.tx_history[tx_hash] = copy.copy(tx)
522                     line = self.tx_history.get(tx_hash)
523                 else:
524                     line['value'] += tx['value']
525                 if line['blk_hash'] == 'mempool':
526                     line['nTime'] = 1e12
527         self.update_tx_labels()
528
529     def update_tx_labels(self):
530         for tx in self.tx_history.values():
531             default_label = ''
532             if tx['value']<0:
533                 for o_addr in tx['outputs']:
534                     if not self.is_change(o_addr):
535                         dest_label = self.labels.get(o_addr)
536                         if dest_label:
537                             default_label = 'to: ' + dest_label
538                         else:
539                             default_label = 'to: ' + o_addr
540             else:
541                 for o_addr in tx['outputs']:
542                     if self.is_mine(o_addr) and not self.is_change(o_addr):
543                         dest_label = self.labels.get(o_addr)
544                         if dest_label:
545                             default_label = 'at: ' + dest_label
546                         else:
547                             default_label = 'at: ' + o_addr
548             tx['default_label'] = default_label
549
550
551
552     def send(self, to_address, amount, label, password):
553         try:
554             inputs, outputs = wallet.choose_inputs_outputs( to_address, amount, self.fee, password )
555         except InvalidPassword:  return False, "Wrong password"
556         if not inputs:  return False, "Not enough funds"
557         try:
558             s_inputs = wallet.sign_inputs( inputs, outputs, password )
559         except InvalidPassword:
560             return False, "Wrong password"
561         tx = raw_tx( s_inputs, outputs )
562         tx = filter( tx )
563         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
564         out = self.send_tx(tx)
565         if out != tx_hash:
566             return False, "error: hash mismatch"
567         if to_address not in self.addressbook:
568             self.addressbook.append(to_address)
569         if label: 
570             wallet.labels[tx_hash] = label
571         wallet.save()
572         return True, tx_hash
573
574
575 if __name__ == '__main__':
576     try:
577         cmd = sys.argv[1]
578     except:
579         cmd = "gui"
580
581     known_commands = ['balance', 'sendtoaddress', 'password', 'getnewaddress', 'addresses', 'history', 'label', 'gui', 'all_addresses']
582     if cmd not in known_commands:
583         print "Known commands:", ', '.join(known_commands)
584         exit(0)
585
586     wallet = Wallet()
587     if cmd=='gui':
588         import gui
589         gui.init_wallet(wallet)
590         gui = gui.BitcoinGUI(wallet)
591         gui.main()
592
593     if not wallet.read():
594         print no_wallet_message
595         seed = raw_input("Enter seed: ")
596         if len(seed)<20:
597             print "Seed too short. Please at least 20 characters"
598             exit(1)
599         if has_encryption:
600             password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
601             if password:
602                 password2 = getpass.getpass("Confirm password:")
603                 if password != password2:
604                     print "error"
605                     exit(1)
606         else:
607             password = None
608             print "in order to use wallet encryption, please install pycrypto  (sudo easy_install pycrypto)"
609
610         wallet.seed = wallet.pw_encode( seed, password)
611
612         print "server name and port number (default: ecdsa.org:50000)"
613         host = raw_input("server:")
614         if not host: host = 'ecdsa.org'
615
616         port = raw_input("port:")
617         if not port: port = 50000
618         else: port = int(port)
619
620         print "default fee for transactions (default 0.005)"
621         fee = raw_input("default fee:")
622         if not fee: fee = 0.005 
623
624         wallet.fee = fee
625         wallet.gap_limit = 5
626         wallet.host = host
627         wallet.port = port
628         wallet.recover(password)
629         wallet.save()
630
631     wallet.new_session()
632     wallet.update()
633     wallet.save()
634
635     if cmd in ['sendtoaddress', 'password', 'getnewaddress']:
636         password = getpass.getpass('Password:') if wallet.use_encryption else None
637
638     if cmd == 'balance':
639         c, u = wallet.get_balance()
640         if u:
641             print c*1e-8, u*1e-8
642         else:
643             print c*1e-8
644
645     elif cmd in [ 'addresses', 'all_addresses']:
646         for addr in wallet.addresses:
647             if cmd == 'all_addresses' or not wallet.is_change(addr):
648                 label = wallet.labels.get(addr) if not wallet.is_change(addr) else "[change]"
649                 if label is None: label = ''
650                 h = wallet.history.get(addr)
651                 ni = no = 0
652                 for item in h:
653                     if item['is_in']:  ni += 1
654                     else:              no += 1
655                 print addr, no, ni, wallet.get_addr_balance(addr)[0]*1e-8, label
656
657     if cmd == 'history':
658         lines = wallet.get_tx_history()
659         b = 0 
660         for line in lines:
661             import datetime
662             v = 1.*line['value']/1e8
663             b += v
664             v_str = "%f"%v if v<0 else "+%f"%v
665             try:
666                 time_str = datetime.datetime.fromtimestamp( line['nTime']) 
667             except:
668                 print line['nTime']
669                 time_str = 'pending'
670             label = line.get('label')
671             if not label: label = line['tx_hash']
672             else: label = label + ' '*(64 - len(label) )
673
674             print time_str, " ", label, " ", v_str, " ", "%f"%b
675         print "# balance: ", b
676
677     elif cmd == 'label':
678         try:
679             tx = sys.argv[2]
680             label = ' '.join(sys.argv[3:])
681         except:
682             print "syntax:  label <tx_hash> <text>"
683             exit(1)
684         wallet.labels[tx] = label
685         wallet.save()
686             
687     elif cmd == 'sendtoaddress':
688         try:
689             to_address = sys.argv[2]
690             amount = float(sys.argv[3])
691             label = ' '.join(sys.argv[4:])
692         except:
693             print "syntax: send <recipient> <amount> [label]"
694             exit(1)
695         r, h = wallet.send( to_address, amount, label, password )
696         print h 
697
698     elif cmd == 'getnewaddress':
699         a = wallet.get_new_address()
700         if a: 
701             print a
702         else:
703             print "Maximum gap reached. Increase gap in order to create more addresses."
704
705     elif cmd == 'password':
706         try:
707             seed = wallet.pw_decode( wallet.seed, password)
708             private_keys = ast.literal_eval( wallet.pw_decode( wallet.private_keys, password) )
709         except:
710             print "sorry"
711             exit(1)
712         new_password = getpass.getpass('New password:')
713         if new_password == getpass.getpass('Confirm new password:'):
714             wallet.use_encryption = (new_password != '')
715             wallet.seed = wallet.pw_encode( seed, new_password)
716             wallet.private_keys = wallet.pw_encode( repr( private_keys ), new_password)
717             wallet.save()
718         else:
719             print "error: mismatch"
720