2012-01-19 08:11:36 -08:00
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2011 thomasv@gitorious
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2012-03-23 13:12:16 -07:00
import sys , base64 , os , re , hashlib , copy , operator , ast , threading , random
2012-05-14 05:10:35 -07:00
import aes , ecdsa
2012-05-14 05:09:50 -07:00
from ecdsa . util import string_to_number , number_to_string
2012-01-19 08:11:36 -08:00
############ functions from pywallet #####################
addrtype = 0
def hash_160 ( public_key ) :
2012-04-03 07:52:15 -07:00
try :
md = hashlib . new ( ' ripemd160 ' )
md . update ( hashlib . sha256 ( public_key ) . digest ( ) )
return md . digest ( )
except :
import ripemd
md = ripemd . new ( hashlib . sha256 ( public_key ) . digest ( ) )
return md . digest ( )
2012-01-19 08:11:36 -08:00
def public_key_to_bc_address ( public_key ) :
h160 = hash_160 ( public_key )
return hash_160_to_bc_address ( h160 )
def hash_160_to_bc_address ( h160 ) :
vh160 = chr ( addrtype ) + h160
h = Hash ( vh160 )
addr = vh160 + h [ 0 : 4 ]
return b58encode ( addr )
def bc_address_to_hash_160 ( addr ) :
bytes = b58decode ( addr , 25 )
return bytes [ 1 : 21 ]
__b58chars = ' 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz '
__b58base = len ( __b58chars )
def b58encode ( v ) :
""" encode v, which is a string of bytes, to base58.
"""
long_value = 0 L
for ( i , c ) in enumerate ( v [ : : - 1 ] ) :
long_value + = ( 256 * * i ) * ord ( c )
result = ' '
while long_value > = __b58base :
div , mod = divmod ( long_value , __b58base )
result = __b58chars [ mod ] + result
long_value = div
result = __b58chars [ long_value ] + result
# Bitcoin does a little leading-zero-compression:
# leading 0-bytes in the input become leading-1s
nPad = 0
for c in v :
if c == ' \0 ' : nPad + = 1
else : break
return ( __b58chars [ 0 ] * nPad ) + result
def b58decode ( v , length ) :
""" decode v into a string of len bytes
"""
long_value = 0 L
for ( i , c ) in enumerate ( v [ : : - 1 ] ) :
long_value + = __b58chars . find ( c ) * ( __b58base * * i )
result = ' '
while long_value > = 256 :
div , mod = divmod ( long_value , 256 )
result = chr ( mod ) + result
long_value = div
result = chr ( long_value ) + result
nPad = 0
for c in v :
if c == __b58chars [ 0 ] : nPad + = 1
else : break
result = chr ( 0 ) * nPad + result
if length is not None and len ( result ) != length :
return None
return result
def Hash ( data ) :
return hashlib . sha256 ( hashlib . sha256 ( data ) . digest ( ) ) . digest ( )
def EncodeBase58Check ( vchIn ) :
hash = Hash ( vchIn )
return b58encode ( vchIn + hash [ 0 : 4 ] )
def DecodeBase58Check ( psz ) :
vchRet = b58decode ( psz , None )
key = vchRet [ 0 : - 4 ]
csum = vchRet [ - 4 : ]
hash = Hash ( key )
cs32 = hash [ 0 : 4 ]
if cs32 != csum :
return None
else :
return key
def PrivKeyToSecret ( privkey ) :
return privkey [ 9 : 9 + 32 ]
def SecretToASecret ( secret ) :
vchIn = chr ( addrtype + 128 ) + secret
return EncodeBase58Check ( vchIn )
def ASecretToSecret ( key ) :
vch = DecodeBase58Check ( key )
if vch and vch [ 0 ] == chr ( addrtype + 128 ) :
return vch [ 1 : ]
else :
return False
########### end pywallet functions #######################
2012-02-14 03:45:39 -08:00
# URL decode
_ud = re . compile ( ' % ([0-9a-hA-H] {2} ) ' , re . MULTILINE )
urldecode = lambda x : _ud . sub ( lambda m : chr ( int ( m . group ( 1 ) , 16 ) ) , x )
2012-01-19 08:11:36 -08:00
def int_to_hex ( i , length = 1 ) :
s = hex ( i ) [ 2 : ] . rstrip ( ' L ' )
s = " 0 " * ( 2 * length - len ( s ) ) + s
return s . decode ( ' hex ' ) [ : : - 1 ] . encode ( ' hex ' )
# AES
EncodeAES = lambda secret , s : base64 . b64encode ( aes . encryptData ( secret , s ) )
DecodeAES = lambda secret , e : aes . decryptData ( secret , base64 . b64decode ( e ) )
# secp256k1, http://www.oid-info.com/get/1.3.132.0.10
_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F L
_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 L
_b = 0x0000000000000000000000000000000000000000000000000000000000000007 L
_a = 0x0000000000000000000000000000000000000000000000000000000000000000 L
_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 L
_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 L
curve_secp256k1 = ecdsa . ellipticcurve . CurveFp ( _p , _a , _b )
generator_secp256k1 = ecdsa . ellipticcurve . Point ( curve_secp256k1 , _Gx , _Gy , _r )
oid_secp256k1 = ( 1 , 3 , 132 , 0 , 10 )
SECP256k1 = ecdsa . curves . Curve ( " SECP256k1 " , curve_secp256k1 , generator_secp256k1 , oid_secp256k1 )
def filter ( s ) :
out = re . sub ( ' ( [^ \n ]*|) \n ' , ' ' , s )
out = out . replace ( ' ' , ' ' )
out = out . replace ( ' \n ' , ' ' )
return out
def raw_tx ( inputs , outputs , for_sig = None ) :
s = int_to_hex ( 1 , 4 ) + ' version \n '
s + = int_to_hex ( len ( inputs ) ) + ' number of inputs \n '
for i in range ( len ( inputs ) ) :
_ , _ , p_hash , p_index , p_script , pubkey , sig = inputs [ i ]
s + = p_hash . decode ( ' hex ' ) [ : : - 1 ] . encode ( ' hex ' ) + ' prev hash \n '
s + = int_to_hex ( p_index , 4 ) + ' prev index \n '
if for_sig is None :
sig = sig + chr ( 1 ) # hashtype
script = int_to_hex ( len ( sig ) ) + ' push %d bytes \n ' % len ( sig )
script + = sig . encode ( ' hex ' ) + ' sig \n '
pubkey = chr ( 4 ) + pubkey
script + = int_to_hex ( len ( pubkey ) ) + ' push %d bytes \n ' % len ( pubkey )
script + = pubkey . encode ( ' hex ' ) + ' pubkey \n '
elif for_sig == i :
script = p_script + ' scriptsig \n '
else :
script = ' '
s + = int_to_hex ( len ( filter ( script ) ) / 2 ) + ' script length \n '
s + = script
s + = " ffffffff " + ' sequence \n '
s + = int_to_hex ( len ( outputs ) ) + ' number of outputs \n '
for output in outputs :
addr , amount = output
s + = int_to_hex ( amount , 8 ) + ' amount: %d \n ' % amount
script = ' 76a9 ' # op_dup, op_hash_160
script + = ' 14 ' # push 0x14 bytes
script + = bc_address_to_hash_160 ( addr ) . encode ( ' hex ' )
script + = ' 88ac ' # op_equalverify, op_checksig
s + = int_to_hex ( len ( filter ( script ) ) / 2 ) + ' script length \n '
s + = script + ' script \n '
s + = int_to_hex ( 0 , 4 ) # lock time
if for_sig is not None : s + = int_to_hex ( 1 , 4 ) # hash type
return s
2012-05-02 08:40:39 -07:00
def format_satoshis ( x , is_diff = False , num_zeros = 0 ) :
2012-02-05 22:48:52 -08:00
from decimal import Decimal
2012-05-19 08:34:59 -07:00
s = Decimal ( x )
sign , digits , exp = s . as_tuple ( )
digits = map ( str , digits )
while len ( digits ) < 9 :
digits . insert ( 0 , ' 0 ' )
digits . insert ( - 8 , ' . ' )
s = ' ' . join ( digits ) . rstrip ( ' 0 ' )
if sign :
s = ' - ' + s
elif is_diff :
2012-02-05 22:48:52 -08:00
s = " + " + s
2012-05-19 08:34:59 -07:00
2012-02-05 22:48:52 -08:00
p = s . find ( ' . ' )
2012-05-02 08:40:39 -07:00
s + = " 0 " * ( 1 + num_zeros - ( len ( s ) - p ) )
2012-02-05 22:48:52 -08:00
s + = " " * ( 9 - ( len ( s ) - p ) )
s = " " * ( 5 - ( p ) ) + s
return s
2012-01-19 08:11:36 -08:00
2012-02-05 22:48:52 -08:00
from version import ELECTRUM_VERSION , SEED_VERSION
2012-03-31 07:35:54 -07:00
from interface import DEFAULT_SERVERS
2012-03-23 13:12:16 -07:00
2012-01-19 08:11:36 -08:00
2012-02-03 06:45:41 -08:00
2012-01-19 08:11:36 -08:00
class Wallet :
2012-04-07 06:13:18 -07:00
def __init__ ( self , gui_callback = lambda : None ) :
2012-01-19 08:11:36 -08:00
self . electrum_version = ELECTRUM_VERSION
self . seed_version = SEED_VERSION
2012-04-07 06:13:18 -07:00
self . gui_callback = gui_callback
2012-01-19 08:11:36 -08:00
self . gap_limit = 5 # configuration
self . fee = 100000
2012-05-02 09:12:32 -07:00
self . num_zeros = 0
2012-01-19 08:11:36 -08:00
self . master_public_key = ' '
# saved fields
self . use_encryption = False
self . addresses = [ ] # receiving addresses visible for user
self . change_addresses = [ ] # addresses used as change
self . seed = ' ' # encrypted
self . history = { }
self . labels = { } # labels for addresses and transactions
2012-02-03 07:39:27 -08:00
self . aliases = { } # aliases for addresses
2012-02-06 08:59:31 -08:00
self . authorities = { } # trusted addresses
2012-05-05 02:33:53 -07:00
self . frozen_addresses = [ ]
2012-06-06 06:40:57 -07:00
self . prioritized_addresses = [ ]
2012-06-07 08:42:50 -07:00
self . expert_mode = False
2012-02-06 08:59:31 -08:00
2012-02-03 08:38:43 -08:00
self . receipts = { } # signed URIs
self . receipt = None # next receipt
2012-01-19 08:11:36 -08:00
self . addressbook = [ ] # outgoing addresses, for payments
# not saved
self . tx_history = { }
self . imported_keys = { }
2012-02-22 06:39:06 -08:00
self . remote_url = None
2012-01-19 08:11:36 -08:00
2012-03-28 05:22:46 -07:00
self . was_updated = True
2012-03-30 08:29:35 -07:00
self . blocks = - 1
2012-03-23 08:34:34 -07:00
self . banner = ' '
2012-05-04 02:14:07 -07:00
# there is a difference between self.up_to_date and self.is_up_to_date()
# self.is_up_to_date() returns true when all requests have been answered and processed
# self.up_to_date is true when the wallet is synchronized (stronger requirement)
2012-03-23 08:34:34 -07:00
self . up_to_date_event = threading . Event ( )
self . up_to_date_event . clear ( )
2012-03-28 05:22:46 -07:00
self . up_to_date = False
2012-03-31 02:47:16 -07:00
self . lock = threading . Lock ( )
2012-03-24 05:15:23 -07:00
self . tx_event = threading . Event ( )
2012-03-23 08:34:34 -07:00
2012-04-01 08:50:12 -07:00
self . pick_random_server ( )
2012-01-19 08:11:36 -08:00
2012-03-30 09:55:19 -07:00
2012-04-01 08:50:12 -07:00
def pick_random_server ( self ) :
self . server = random . choice ( DEFAULT_SERVERS ) # random choice when the wallet is created
2012-03-30 09:55:19 -07:00
2012-03-30 05:15:05 -07:00
def is_up_to_date ( self ) :
2012-05-04 00:28:44 -07:00
return self . interface . responses . empty ( ) and not self . interface . unanswered_requests
2012-03-30 05:15:05 -07:00
def set_server ( self , server ) :
2012-04-03 07:52:15 -07:00
# raise an error if the format isnt correct
a , b , c = server . split ( ' : ' )
b = int ( b )
2012-04-03 10:03:58 -07:00
assert c in [ ' t ' , ' h ' , ' n ' ]
2012-04-03 07:52:15 -07:00
# set the server
2012-03-30 05:15:05 -07:00
if server != self . server :
self . server = server
2012-03-31 05:03:30 -07:00
self . save ( )
2012-03-14 07:35:39 -07:00
self . interface . is_connected = False # this exits the polling loop
2012-05-12 00:38:42 -07:00
self . interface . poke ( )
2012-03-14 07:35:39 -07:00
2012-01-19 08:11:36 -08:00
def set_path ( self , wallet_path ) :
if wallet_path is not None :
self . path = wallet_path
else :
# backward compatibility: look for wallet file in the default data directory
if " HOME " in os . environ :
wallet_dir = os . path . join ( os . environ [ " HOME " ] , ' .electrum ' )
elif " LOCALAPPDATA " in os . environ :
wallet_dir = os . path . join ( os . environ [ " LOCALAPPDATA " ] , ' Electrum ' )
elif " APPDATA " in os . environ :
wallet_dir = os . path . join ( os . environ [ " APPDATA " ] , ' Electrum ' )
else :
raise BaseException ( " No home directory found in environment variables. " )
if not os . path . exists ( wallet_dir ) : os . mkdir ( wallet_dir )
self . path = os . path . join ( wallet_dir , ' electrum.dat ' )
def import_key ( self , keypair , password ) :
address , key = keypair . split ( ' : ' )
2012-05-30 05:42:30 -07:00
if not self . is_valid ( address ) :
raise BaseException ( ' Invalid Bitcoin address ' )
if address in self . all_addresses ( ) :
raise BaseException ( ' Address already in wallet ' )
2012-01-19 08:11:36 -08:00
b = ASecretToSecret ( key )
2012-06-06 09:32:29 -07:00
if not b :
raise BaseException ( ' Unsupported key format ' )
2012-01-19 08:11:36 -08:00
secexp = int ( b . encode ( ' hex ' ) , 16 )
private_key = ecdsa . SigningKey . from_secret_exponent ( secexp , curve = SECP256k1 )
# sanity check
public_key = private_key . get_verifying_key ( )
2012-05-30 05:42:30 -07:00
if not address == public_key_to_bc_address ( ' 04 ' . decode ( ' hex ' ) + public_key . to_string ( ) ) :
raise BaseException ( ' Address does not match private key ' )
2012-01-19 08:11:36 -08:00
self . imported_keys [ address ] = self . pw_encode ( key , password )
def new_seed ( self , password ) :
seed = " %032x " % ecdsa . util . randrange ( pow ( 2 , 128 ) )
2012-04-09 02:38:21 -07:00
#self.init_mpk(seed)
2012-01-19 08:11:36 -08:00
# encrypt
2012-01-19 08:39:24 -08:00
self . seed = self . pw_encode ( seed , password )
2012-01-19 08:11:36 -08:00
2012-02-21 05:36:45 -08:00
2012-01-19 08:11:36 -08:00
def init_mpk ( self , seed ) :
# public key
curve = SECP256k1
secexp = self . stretch_key ( seed )
master_private_key = ecdsa . SigningKey . from_secret_exponent ( secexp , curve = SECP256k1 )
self . master_public_key = master_private_key . get_verifying_key ( ) . to_string ( )
def all_addresses ( self ) :
return self . addresses + self . change_addresses + self . imported_keys . keys ( )
def is_mine ( self , address ) :
return address in self . all_addresses ( )
def is_change ( self , address ) :
return address in self . change_addresses
def is_valid ( self , addr ) :
ADDRESS_RE = re . compile ( ' [1-9A-HJ-NP-Za-km-z] { 26,} \\ Z ' )
if not ADDRESS_RE . match ( addr ) : return False
try :
h = bc_address_to_hash_160 ( addr )
except :
return False
return addr == hash_160_to_bc_address ( h )
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 ) )
2012-05-14 12:31:37 -07:00
def get_private_key_base58 ( self , address , password ) :
pk = self . get_private_key ( address , password )
if pk is None : return None
return SecretToASecret ( pk )
2012-02-06 09:10:30 -08:00
def get_private_key ( self , address , password ) :
2012-01-19 08:11:36 -08:00
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
order = generator_secp256k1 . order ( )
if address in self . imported_keys . keys ( ) :
b = self . pw_decode ( self . imported_keys [ address ] , password )
2012-05-17 09:10:36 -07:00
if not b : return None
2012-01-19 08:11:36 -08:00
b = ASecretToSecret ( b )
secexp = int ( b . encode ( ' hex ' ) , 16 )
else :
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
else :
raise BaseException ( " unknown address " )
2012-02-21 14:27:21 -08:00
try :
seed = self . pw_decode ( self . seed , password )
except :
raise BaseException ( " Invalid password " )
2012-05-16 23:42:40 -07:00
if not seed : return None
2012-01-19 08:11:36 -08:00
secexp = self . stretch_key ( seed )
secexp = ( secexp + self . get_sequence ( n , for_change ) ) % order
pk = number_to_string ( secexp , order )
return pk
2012-02-01 11:27:03 -08:00
2012-02-02 06:26:10 -08:00
def msg_magic ( self , message ) :
return " \x18 Bitcoin Signed Message: \n " + chr ( len ( message ) ) + message
2012-02-01 11:27:03 -08:00
def sign_message ( self , address , message , password ) :
2012-02-06 09:10:30 -08:00
private_key = ecdsa . SigningKey . from_string ( self . get_private_key ( address , password ) , curve = SECP256k1 )
2012-02-01 11:27:03 -08:00
public_key = private_key . get_verifying_key ( )
2012-02-02 06:26:10 -08:00
signature = private_key . sign_digest ( Hash ( self . msg_magic ( message ) ) , sigencode = ecdsa . util . sigencode_string )
assert public_key . verify_digest ( signature , Hash ( self . msg_magic ( message ) ) , sigdecode = ecdsa . util . sigdecode_string )
2012-02-01 11:27:03 -08:00
for i in range ( 4 ) :
sig = base64 . b64encode ( chr ( 27 + i ) + signature )
2012-02-03 02:48:09 -08:00
try :
self . verify_message ( address , sig , message )
2012-02-01 11:27:03 -08:00
return sig
2012-02-03 02:48:09 -08:00
except :
continue
2012-02-01 11:27:03 -08:00
else :
raise BaseException ( " error: cannot sign message " )
2012-01-19 08:11:36 -08:00
2012-02-01 11:27:03 -08:00
def verify_message ( self , address , signature , message ) :
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
from ecdsa import numbertheory , ellipticcurve , util
import msqr
curve = curve_secp256k1
G = generator_secp256k1
order = G . order ( )
2012-02-02 06:26:10 -08:00
# extract r,s from signature
2012-02-01 11:27:03 -08:00
sig = base64 . b64decode ( signature )
2012-02-06 08:59:31 -08:00
if len ( sig ) != 65 : raise BaseException ( " Wrong encoding " )
2012-02-01 11:27:03 -08:00
r , s = util . sigdecode_string ( sig [ 1 : ] , order )
2012-02-02 06:26:10 -08:00
recid = ord ( sig [ 0 ] ) - 27
2012-02-01 11:27:03 -08:00
# 1.1
x = r + ( recid / 2 ) * order
# 1.3
alpha = ( x * x * x + curve . a ( ) * x + curve . b ( ) ) % curve . p ( )
beta = msqr . modular_sqrt ( alpha , curve . p ( ) )
y = beta if ( beta - recid ) % 2 == 0 else curve . p ( ) - beta
# 1.4 the constructor checks that nR is at infinity
2012-02-03 02:48:09 -08:00
R = ellipticcurve . Point ( curve , x , y , order )
2012-02-01 11:27:03 -08:00
# 1.5 compute e from message:
2012-02-02 06:26:10 -08:00
h = Hash ( self . msg_magic ( message ) )
2012-02-01 11:27:03 -08:00
e = string_to_number ( h )
minus_e = - e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory . inverse_mod ( r , order )
Q = inv_r * ( s * R + minus_e * G )
public_key = ecdsa . VerifyingKey . from_public_point ( Q , curve = SECP256k1 )
# check that Q is the public key
2012-02-03 02:48:09 -08:00
public_key . verify_digest ( sig [ 1 : ] , h , sigdecode = ecdsa . util . sigdecode_string )
2012-02-01 11:27:03 -08:00
# check that we get the original signing address
addr = public_key_to_bc_address ( ' 04 ' . decode ( ' hex ' ) + public_key . to_string ( ) )
# print addr
2012-02-06 09:07:01 -08:00
if address != addr :
2012-02-06 09:55:25 -08:00
print " bad signature "
2012-02-06 08:59:31 -08:00
raise BaseException ( " Bad signature " )
2012-01-20 08:21:29 -08:00
2012-01-19 08:11:36 -08:00
2012-03-23 12:58:54 -07:00
def create_new_address ( self , for_change ) :
2012-01-19 08:11:36 -08:00
""" Publickey(type,n) = Master_public_key + H(n|S|type)*point """
curve = SECP256k1
n = len ( self . change_addresses ) if for_change else len ( self . addresses )
z = self . get_sequence ( n , for_change )
master_public_key = ecdsa . VerifyingKey . from_string ( self . master_public_key , 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 ( ) )
if for_change :
self . change_addresses . append ( address )
else :
self . addresses . append ( address )
2012-02-21 05:36:45 -08:00
self . history [ address ] = [ ]
2012-01-19 08:11:36 -08:00
print address
2012-02-21 05:36:45 -08:00
return address
2012-01-19 08:11:36 -08:00
def synchronize ( self ) :
2012-03-23 08:34:34 -07:00
if not self . master_public_key :
2012-03-23 12:58:54 -07:00
return [ ]
2012-02-22 06:39:06 -08:00
2012-03-23 12:58:54 -07:00
new_addresses = [ ]
2012-01-19 08:11:36 -08:00
while True :
if self . change_addresses == [ ] :
2012-03-23 12:58:54 -07:00
new_addresses . append ( self . create_new_address ( True ) )
2012-01-19 08:11:36 -08:00
continue
a = self . change_addresses [ - 1 ]
if self . history . get ( a ) :
2012-03-23 12:58:54 -07:00
new_addresses . append ( self . create_new_address ( True ) )
2012-01-19 08:11:36 -08:00
else :
break
n = self . gap_limit
while True :
if len ( self . addresses ) < n :
2012-03-23 12:58:54 -07:00
new_addresses . append ( self . create_new_address ( False ) )
2012-01-19 08:11:36 -08:00
continue
if map ( lambda a : self . history . get ( a ) , self . addresses [ - n : ] ) == n * [ [ ] ] :
break
else :
2012-03-23 12:58:54 -07:00
new_addresses . append ( self . create_new_address ( False ) )
2012-01-19 08:11:36 -08:00
2012-02-22 06:39:06 -08:00
if self . remote_url :
num = self . get_remote_number ( )
while len ( self . addresses ) < num :
2012-03-23 12:58:54 -07:00
new_addresses . append ( self . create_new_address ( False ) )
2012-02-22 08:11:21 -08:00
2012-03-23 12:58:54 -07:00
return new_addresses
2012-02-22 06:39:06 -08:00
2012-03-23 08:34:34 -07:00
2012-02-22 06:39:06 -08:00
def get_remote_number ( self ) :
import jsonrpclib
server = jsonrpclib . Server ( self . remote_url )
2012-02-22 07:17:24 -08:00
out = server . getnum ( )
2012-02-22 06:39:06 -08:00
return out
def get_remote_mpk ( self ) :
import jsonrpclib
server = jsonrpclib . Server ( self . remote_url )
2012-02-22 07:17:24 -08:00
out = server . getkey ( )
2012-02-22 06:39:06 -08:00
return out
2012-01-19 08:11:36 -08:00
def is_found ( self ) :
return ( len ( self . change_addresses ) > 1 ) or ( len ( self . addresses ) > self . gap_limit )
def fill_addressbook ( self ) :
for tx in self . tx_history . values ( ) :
if tx [ ' value ' ] < 0 :
for i in tx [ ' outputs ' ] :
if not self . is_mine ( i ) and i not in self . addressbook :
self . addressbook . append ( i )
# redo labels
self . update_tx_labels ( )
def save ( self ) :
s = {
' seed_version ' : self . seed_version ,
' use_encryption ' : self . use_encryption ,
' master_public_key ' : self . master_public_key . encode ( ' hex ' ) ,
' fee ' : self . fee ,
2012-03-30 05:15:05 -07:00
' server ' : self . server ,
2012-01-19 08:11:36 -08:00
' seed ' : self . seed ,
' addresses ' : self . addresses ,
' change_addresses ' : self . change_addresses ,
' history ' : self . history ,
' labels ' : self . labels ,
' contacts ' : self . addressbook ,
' imported_keys ' : self . imported_keys ,
2012-02-03 07:39:27 -08:00
' aliases ' : self . aliases ,
2012-02-06 08:59:31 -08:00
' authorities ' : self . authorities ,
2012-02-03 08:38:43 -08:00
' receipts ' : self . receipts ,
2012-05-02 08:40:39 -07:00
' num_zeros ' : self . num_zeros ,
2012-05-05 02:33:53 -07:00
' frozen_addresses ' : self . frozen_addresses ,
2012-06-06 06:40:57 -07:00
' prioritized_addresses ' : self . prioritized_addresses ,
2012-06-07 08:42:50 -07:00
' expert_mode ' : self . expert_mode ,
2012-01-19 08:11:36 -08:00
}
f = open ( self . path , " w " )
f . write ( repr ( s ) )
f . close ( )
def read ( self ) :
2012-03-14 06:55:12 -07:00
import interface
2012-03-12 09:55:33 -07:00
2012-01-19 08:11:36 -08:00
upgrade_msg = """ This wallet seed is deprecated. Please run upgrade.py for a diagnostic. """
2012-03-12 09:55:33 -07:00
self . file_exists = False
2012-01-19 08:11:36 -08:00
try :
f = open ( self . path , " r " )
data = f . read ( )
f . close ( )
except :
2012-03-12 09:55:33 -07:00
return
2012-03-30 01:48:32 -07:00
data = interface . old_to_new ( data )
2012-01-19 08:11:36 -08:00
try :
d = ast . literal_eval ( data )
self . seed_version = d . get ( ' seed_version ' )
self . master_public_key = d . get ( ' master_public_key ' ) . decode ( ' hex ' )
self . use_encryption = d . get ( ' use_encryption ' )
self . fee = int ( d . get ( ' fee ' ) )
self . seed = d . get ( ' seed ' )
2012-03-30 05:15:05 -07:00
self . server = d . get ( ' server ' )
2012-03-31 02:47:16 -07:00
#blocks = d.get('blocks')
2012-01-19 08:11:36 -08:00
self . addresses = d . get ( ' addresses ' )
self . change_addresses = d . get ( ' change_addresses ' )
self . history = d . get ( ' history ' )
self . labels = d . get ( ' labels ' )
self . addressbook = d . get ( ' contacts ' )
self . imported_keys = d . get ( ' imported_keys ' , { } )
2012-02-03 07:39:27 -08:00
self . aliases = d . get ( ' aliases ' , { } )
2012-02-06 08:59:31 -08:00
self . authorities = d . get ( ' authorities ' , { } )
2012-02-03 08:38:43 -08:00
self . receipts = d . get ( ' receipts ' , { } )
2012-05-02 08:40:39 -07:00
self . num_zeros = d . get ( ' num_zeros ' , 0 )
2012-05-05 02:33:53 -07:00
self . frozen_addresses = d . get ( ' frozen_addresses ' , [ ] )
2012-06-06 06:40:57 -07:00
self . prioritized_addresses = d . get ( ' prioritized_addresses ' , [ ] )
2012-06-07 08:42:50 -07:00
self . expert_mode = d . get ( ' expert_mode ' , False )
2012-01-19 08:11:36 -08:00
except :
2012-03-12 09:55:33 -07:00
raise BaseException ( " cannot read wallet file " )
2012-01-19 08:11:36 -08:00
self . update_tx_history ( )
if self . seed_version != SEED_VERSION :
raise BaseException ( upgrade_msg )
2012-02-22 06:39:06 -08:00
if self . remote_url : assert self . master_public_key . encode ( ' hex ' ) == self . get_remote_mpk ( )
2012-03-12 09:55:33 -07:00
self . file_exists = True
2012-03-29 10:11:05 -07:00
2012-03-12 09:55:33 -07:00
2012-06-07 07:14:08 -07:00
def get_address_flags ( self , addr ) :
flags = " C " if self . is_change ( addr ) else " I " if addr in self . imported_keys . keys ( ) else " - "
flags + = " F " if addr in self . frozen_addresses else " P " if addr in self . prioritized_addresses else " - "
return flags
2012-01-19 08:11:36 -08:00
def get_addr_balance ( self , addr ) :
2012-05-01 01:55:10 -07:00
assert self . is_mine ( addr )
h = self . history . get ( addr , [ ] )
2012-01-19 08:11:36 -08:00
c = u = 0
for item in h :
v = item [ ' value ' ]
if item [ ' height ' ] :
c + = v
else :
u + = v
return c , u
def get_balance ( self ) :
conf = unconf = 0
for addr in self . all_addresses ( ) :
c , u = self . get_addr_balance ( addr )
conf + = c
unconf + = u
return conf , unconf
2012-02-10 04:23:39 -08:00
def choose_tx_inputs ( self , amount , fixed_fee , from_addr = None ) :
2012-01-19 08:11:36 -08:00
""" todo: minimize tx size """
total = 0
fee = self . fee if fixed_fee is None else fixed_fee
coins = [ ]
2012-06-06 06:40:57 -07:00
prioritized_coins = [ ]
2012-02-10 04:23:39 -08:00
domain = [ from_addr ] if from_addr else self . all_addresses ( )
2012-05-05 02:33:53 -07:00
for i in self . frozen_addresses :
if i in domain : domain . remove ( i )
2012-06-06 06:40:57 -07:00
for i in self . prioritized_addresses :
if i in domain : domain . remove ( i )
2012-02-10 04:23:39 -08:00
for addr in domain :
2012-01-19 08:11:36 -08:00
h = self . history . get ( addr )
if h is None : continue
for item in h :
2012-03-30 01:48:32 -07:00
if item . get ( ' raw_output_script ' ) :
2012-01-19 08:11:36 -08:00
coins . append ( ( addr , item ) )
2012-03-30 01:48:32 -07:00
coins = sorted ( coins , key = lambda x : x [ 1 ] [ ' timestamp ' ] )
2012-06-06 06:40:57 -07:00
2012-06-06 10:26:05 -07:00
for addr in self . prioritized_addresses :
2012-06-06 06:40:57 -07:00
h = self . history . get ( addr )
if h is None : continue
for item in h :
if item . get ( ' raw_output_script ' ) :
prioritized_coins . append ( ( addr , item ) )
prioritized_coins = sorted ( prioritized_coins , key = lambda x : x [ 1 ] [ ' timestamp ' ] )
2012-01-19 08:11:36 -08:00
inputs = [ ]
2012-06-06 06:40:57 -07:00
coins = prioritized_coins + coins
2012-01-19 08:11:36 -08:00
for c in coins :
addr , item = c
v = item . get ( ' value ' )
total + = v
2012-03-30 01:48:32 -07:00
inputs . append ( ( addr , v , item [ ' tx_hash ' ] , item [ ' index ' ] , item [ ' raw_output_script ' ] , None , None ) )
2012-01-19 08:11:36 -08:00
fee = self . fee * len ( inputs ) if fixed_fee is None else fixed_fee
if total > = amount + fee : break
else :
#print "not enough funds: %d %d"%(total, fee)
inputs = [ ]
return inputs , total , fee
2012-02-07 19:22:18 -08:00
def choose_tx_outputs ( self , to_addr , amount , fee , total , change_addr = None ) :
2012-01-19 08:11:36 -08:00
outputs = [ ( to_addr , amount ) ]
change_amount = total - ( amount + fee )
if change_amount != 0 :
# normally, the update thread should ensure that the last change address is unused
2012-02-07 19:22:18 -08:00
if not change_addr :
change_addr = self . change_addresses [ - 1 ]
outputs . append ( ( change_addr , change_amount ) )
2012-01-19 08:11:36 -08:00
return outputs
def sign_inputs ( self , inputs , outputs , password ) :
s_inputs = [ ]
for i in range ( len ( inputs ) ) :
addr , v , p_hash , p_pos , p_scriptPubKey , _ , _ = inputs [ i ]
2012-02-06 09:10:30 -08:00
private_key = ecdsa . SigningKey . from_string ( self . get_private_key ( addr , password ) , curve = SECP256k1 )
2012-01-19 08:11:36 -08:00
public_key = private_key . get_verifying_key ( )
pubkey = public_key . to_string ( )
tx = filter ( raw_tx ( inputs , outputs , for_sig = i ) )
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 )
s_inputs . append ( ( addr , v , p_hash , p_pos , p_scriptPubKey , pubkey , sig ) )
return s_inputs
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 )
d = DecodeAES ( secret , s )
if s == self . seed :
try :
d . decode ( ' hex ' )
except :
raise BaseException ( " Invalid password " )
return d
else :
return s
2012-03-30 05:15:05 -07:00
def get_status ( self , address ) :
h = self . history . get ( address )
if not h :
status = None
else :
lastpoint = h [ - 1 ]
status = lastpoint [ ' block_hash ' ]
if status == ' mempool ' :
status = status + ' : %d ' % len ( h )
return status
2012-03-16 14:24:28 -07:00
def receive_status_callback ( self , addr , status ) :
2012-03-31 02:47:16 -07:00
with self . lock :
if self . get_status ( addr ) != status :
#print "updating status for", addr, status
self . interface . get_history ( addr )
def receive_history_callback ( self , addr , data ) :
2012-03-20 07:30:36 -07:00
#print "updating history for", addr
2012-03-31 02:47:16 -07:00
with self . lock :
self . history [ addr ] = data
self . update_tx_history ( )
self . save ( )
2012-03-16 14:18:58 -07:00
2012-01-19 08:11:36 -08:00
def get_tx_history ( self ) :
lines = self . tx_history . values ( )
2012-03-30 01:48:32 -07:00
lines = sorted ( lines , key = operator . itemgetter ( " timestamp " ) )
2012-01-19 08:11:36 -08:00
return lines
def update_tx_history ( self ) :
self . tx_history = { }
for addr in self . all_addresses ( ) :
h = self . history . get ( addr )
if h is None : continue
for tx in h :
tx_hash = tx [ ' tx_hash ' ]
line = self . tx_history . get ( tx_hash )
if not line :
self . tx_history [ tx_hash ] = copy . copy ( tx )
line = self . tx_history . get ( tx_hash )
else :
line [ ' value ' ] + = tx [ ' value ' ]
if line [ ' height ' ] == 0 :
2012-03-30 01:48:32 -07:00
line [ ' timestamp ' ] = 1e12
2012-01-19 08:11:36 -08:00
self . update_tx_labels ( )
def update_tx_labels ( self ) :
for tx in self . tx_history . values ( ) :
default_label = ' '
if tx [ ' value ' ] < 0 :
for o_addr in tx [ ' outputs ' ] :
if not self . is_change ( o_addr ) :
dest_label = self . labels . get ( o_addr )
if dest_label :
default_label = ' to: ' + dest_label
else :
default_label = ' to: ' + o_addr
else :
for o_addr in tx [ ' outputs ' ] :
if self . is_mine ( o_addr ) and not self . is_change ( o_addr ) :
2012-05-19 02:01:45 -07:00
break
else :
for o_addr in tx [ ' outputs ' ] :
if self . is_mine ( o_addr ) :
break
else :
o_addr = None
if o_addr :
dest_label = self . labels . get ( o_addr )
if dest_label :
default_label = ' at: ' + dest_label
else :
default_label = ' at: ' + o_addr
2012-01-19 08:11:36 -08:00
tx [ ' default_label ' ] = default_label
2012-02-10 04:23:39 -08:00
def mktx ( self , to_address , amount , label , password , fee = None , change_addr = None , from_addr = None ) :
2012-01-19 08:11:36 -08:00
if not self . is_valid ( to_address ) :
raise BaseException ( " Invalid address " )
2012-02-10 04:23:39 -08:00
inputs , total , fee = self . choose_tx_inputs ( amount , fee , from_addr )
2012-01-19 08:11:36 -08:00
if not inputs :
raise BaseException ( " Not enough funds " )
2012-02-07 19:22:18 -08:00
outputs = self . choose_tx_outputs ( to_address , amount , fee , total , change_addr )
2012-01-20 08:21:29 -08:00
s_inputs = self . sign_inputs ( inputs , outputs , password )
2012-01-19 08:11:36 -08:00
tx = filter ( raw_tx ( s_inputs , outputs ) )
if to_address not in self . addressbook :
self . addressbook . append ( to_address )
if label :
tx_hash = Hash ( tx . decode ( ' hex ' ) ) [ : : - 1 ] . encode ( ' hex ' )
2012-01-20 08:21:29 -08:00
self . labels [ tx_hash ] = label
2012-02-10 04:44:10 -08:00
2012-01-19 08:11:36 -08:00
return tx
def sendtx ( self , tx ) :
tx_hash = Hash ( tx . decode ( ' hex ' ) ) [ : : - 1 ] . encode ( ' hex ' )
2012-03-24 05:15:23 -07:00
self . tx_event . clear ( )
2012-04-01 03:38:01 -07:00
self . interface . send ( [ ( ' blockchain.transaction.broadcast ' , [ tx ] ) ] )
2012-03-24 05:15:23 -07:00
self . tx_event . wait ( )
out = self . tx_result
2012-01-19 08:11:36 -08:00
if out != tx_hash :
return False , " error: " + out
2012-02-03 08:38:43 -08:00
if self . receipt :
self . receipts [ tx_hash ] = self . receipt
self . receipt = None
2012-01-19 08:11:36 -08:00
return True , out
2012-02-06 08:59:31 -08:00
def read_alias ( self , alias ) :
2012-02-03 06:45:41 -08:00
# this might not be the right place for this function.
import urllib
2012-02-06 14:45:21 -08:00
m1 = re . match ( ' ([ \ w \ - \ .]+)@(( \ w[ \ w \ -]+ \ .)+[ \ w \ -]+) ' , alias )
m2 = re . match ( ' (( \ w[ \ w \ -]+ \ .)+[ \ w \ -]+) ' , alias )
if m1 :
url = ' http:// ' + m1 . group ( 2 ) + ' /bitcoin.id/ ' + m1 . group ( 1 )
elif m2 :
url = ' http:// ' + alias + ' /bitcoin.id '
2012-02-03 06:45:41 -08:00
else :
2012-02-06 14:45:21 -08:00
return ' '
try :
lines = urllib . urlopen ( url ) . readlines ( )
except :
return ' '
2012-02-03 07:39:27 -08:00
2012-02-06 14:45:21 -08:00
# line 0
line = lines [ 0 ] . strip ( ) . split ( ' : ' )
if len ( line ) == 1 :
auth_name = None
target = signing_addr = line [ 0 ]
else :
target , auth_name , signing_addr , signature = line
msg = " alias: %s : %s : %s " % ( alias , target , auth_name )
print msg , signature
self . verify_message ( signing_addr , signature , msg )
# other lines are signed updates
for line in lines [ 1 : ] :
line = line . strip ( )
if not line : continue
line = line . split ( ' : ' )
previous = target
print repr ( line )
target , signature = line
self . verify_message ( previous , signature , " alias: %s : %s " % ( alias , target ) )
if not self . is_valid ( target ) :
raise BaseException ( " Invalid bitcoin address " )
return target , signing_addr , auth_name
2012-02-13 05:52:59 -08:00
2012-05-16 23:32:49 -07:00
def update_password ( self , seed , old_password , new_password ) :
2012-04-09 02:38:21 -07:00
if new_password == ' ' : new_password = None
self . use_encryption = ( new_password != None )
2012-02-13 05:52:59 -08:00
self . seed = self . pw_encode ( seed , new_password )
for k in self . imported_keys . keys ( ) :
a = self . imported_keys [ k ]
2012-05-16 23:32:49 -07:00
b = self . pw_decode ( a , old_password )
2012-02-13 05:52:59 -08:00
c = self . pw_encode ( b , new_password )
2012-02-13 05:56:31 -08:00
self . imported_keys [ k ] = c
self . save ( )
2012-02-14 00:52:03 -08:00
def get_alias ( self , alias , interactive = False , show_message = None , question = None ) :
try :
target , signing_address , auth_name = self . read_alias ( alias )
except BaseException , e :
# raise exception if verify fails (verify the chain)
if interactive :
2012-05-16 10:20:21 -07:00
show_message ( " Alias error: " + str ( e ) )
2012-02-14 00:52:03 -08:00
return
print target , signing_address , auth_name
if auth_name is None :
a = self . aliases . get ( alias )
if not a :
msg = " Warning: the alias ' %s ' is self-signed. \n The signing address is %s . \n \n Do you want to add this alias to your list of contacts? " % ( alias , signing_address )
if interactive and question ( msg ) :
self . aliases [ alias ] = ( signing_address , target )
else :
target = None
else :
if signing_address != a [ 0 ] :
msg = " Warning: the key of alias ' %s ' has changed since your last visit! It is possible that someone is trying to do something nasty!!! \n Do you accept to change your trusted key? " % alias
if interactive and question ( msg ) :
self . aliases [ alias ] = ( signing_address , target )
else :
target = None
else :
if signing_address not in self . authorities . keys ( ) :
msg = " The alias: ' %s ' links to %s \n \n Warning: this alias was signed by an unknown key. \n Signing authority: %s \n Signing address: %s \n \n Do you want to add this key to your list of trusted keys? " % ( alias , target , auth_name , signing_address )
if interactive and question ( msg ) :
self . authorities [ signing_address ] = auth_name
else :
target = None
if target :
self . aliases [ alias ] = ( signing_address , target )
return target
2012-02-14 03:45:39 -08:00
2012-02-14 04:49:05 -08:00
def parse_url ( self , url , show_message , question ) :
2012-02-14 03:45:39 -08:00
o = url [ 8 : ] . split ( ' ? ' )
address = o [ 0 ]
if len ( o ) > 1 :
params = o [ 1 ] . split ( ' & ' )
else :
params = [ ]
amount = label = message = signature = identity = ' '
for p in params :
k , v = p . split ( ' = ' )
uv = urldecode ( v )
if k == ' amount ' : amount = uv
elif k == ' message ' : message = uv
elif k == ' label ' : label = uv
elif k == ' signature ' :
identity , signature = uv . split ( ' : ' )
url = url . replace ( ' & %s = %s ' % ( k , v ) , ' ' )
else :
print k , v
2012-02-14 04:49:05 -08:00
if signature :
if re . match ( ' ^(|([ \ w \ - \ .]+)@)(( \ w[ \ w \ -]+ \ .)+[ \ w \ -]+)$ ' , identity ) :
signing_address = self . get_alias ( identity , True , show_message , question )
elif self . is_valid ( identity ) :
signing_address = identity
else :
signing_address = None
if not signing_address :
return
try :
self . verify_message ( signing_address , signature , url )
self . receipt = ( signing_address , signature , url )
except :
2012-02-14 08:17:59 -08:00
show_message ( ' Warning: the URI contains a bad signature. \n The identity of the recipient cannot be verified. ' )
2012-02-14 04:49:05 -08:00
address = amount = label = identity = message = ' '
if re . match ( ' ^(|([ \ w \ - \ .]+)@)(( \ w[ \ w \ -]+ \ .)+[ \ w \ -]+)$ ' , address ) :
2012-02-14 08:16:28 -08:00
payto_address = self . get_alias ( address , True , show_message , question )
2012-02-14 04:49:05 -08:00
if payto_address :
address = address + ' < ' + payto_address + ' > '
2012-02-14 04:38:47 -08:00
return address , amount , label , message , signature , identity , url
2012-03-23 08:34:34 -07:00
def update ( self ) :
2012-03-30 05:15:05 -07:00
self . interface . poke ( )
2012-05-19 06:52:06 -07:00
self . up_to_date_event . wait ( 10000000000 )
2012-03-23 08:34:34 -07:00
2012-03-23 09:46:48 -07:00
2012-05-02 05:21:58 -07:00
def start_session ( self , interface ) :
self . interface = interface
self . interface . send ( [ ( ' server.banner ' , [ ] ) , ( ' blockchain.numblocks.subscribe ' , [ ] ) , ( ' server.peers.subscribe ' , [ ] ) ] )
2012-05-04 00:28:44 -07:00
self . interface . subscribe ( self . all_addresses ( ) )
2012-05-02 05:21:58 -07:00
2012-06-07 02:18:11 -07:00
def freeze ( self , addr ) :
if addr in self . all_addresses ( ) and addr not in self . frozen_addresses :
2012-06-07 02:21:53 -07:00
self . unprioritize ( addr )
2012-06-07 02:18:11 -07:00
self . frozen_addresses . append ( addr )
self . save ( )
return True
else :
return False
2012-05-02 05:21:58 -07:00
2012-06-07 02:18:11 -07:00
def unfreeze ( self , addr ) :
if addr in self . all_addresses ( ) and addr in self . frozen_addresses :
self . frozen_addresses . remove ( addr )
self . save ( )
return True
else :
return False
2012-03-30 05:15:05 -07:00
2012-06-07 02:18:11 -07:00
def prioritize ( self , addr ) :
2012-06-07 02:25:23 -07:00
if addr in self . all_addresses ( ) and addr not in self . prioritized_addresses :
self . unfreeze ( addr )
2012-06-07 02:18:11 -07:00
self . prioritized_addresses . append ( addr )
self . save ( )
return True
else :
return False
def unprioritize ( self , addr ) :
if addr in self . all_addresses ( ) and addr in self . prioritized_addresses :
self . prioritized_addresses . remove ( addr )
self . save ( )
return True
else :
return False