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-08-23 18:21:17 -07:00
import sys
import base64
import os
import re
import hashlib
import copy
import operator
import ast
import threading
import random
import aes
2012-10-20 17:57:31 -07:00
import Queue
2012-10-22 08:18:07 -07:00
import time
2012-01-19 08:11:36 -08:00
2013-02-23 02:35:46 -08:00
from util import print_msg , print_error , user_dir , format_satoshis
2012-10-19 05:55:01 -07:00
from bitcoin import *
2013-08-01 11:08:56 -07:00
from account import *
2012-02-14 03:45:39 -08:00
2012-10-19 05:55:01 -07:00
# AES encryption
2012-01-19 08:11:36 -08:00
EncodeAES = lambda secret , s : base64 . b64encode ( aes . encryptData ( secret , s ) )
DecodeAES = lambda secret , e : aes . decryptData ( secret , base64 . b64decode ( e ) )
2013-02-23 02:35:46 -08:00
def pw_encode ( s , password ) :
if password :
secret = Hash ( password )
return EncodeAES ( secret , s )
else :
return s
def pw_decode ( s , password ) :
if password is not None :
secret = Hash ( password )
try :
d = DecodeAES ( secret , s )
except :
raise BaseException ( ' Invalid password ' )
return d
else :
return s
2012-01-19 08:11:36 -08:00
2012-02-05 22:48:52 -08:00
from version import ELECTRUM_VERSION , SEED_VERSION
2012-01-19 08:11:36 -08:00
class Wallet :
2012-10-11 11:10:12 -07:00
def __init__ ( self , config = { } ) :
2012-01-19 08:11:36 -08:00
2012-10-11 11:10:12 -07:00
self . config = config
2012-01-19 08:11:36 -08:00
self . electrum_version = ELECTRUM_VERSION
2013-01-29 05:53:13 -08:00
self . gap_limit_for_change = 3 # constant
2012-01-19 08:11:36 -08:00
# saved fields
2012-10-11 11:10:12 -07:00
self . seed_version = config . get ( ' seed_version ' , SEED_VERSION )
self . gap_limit = config . get ( ' gap_limit ' , 5 )
self . use_change = config . get ( ' use_change ' , True )
2013-05-10 05:36:38 -07:00
self . fee = int ( config . get ( ' fee_per_kb ' , 50000 ) )
2012-10-11 11:10:12 -07:00
self . num_zeros = int ( config . get ( ' num_zeros ' , 0 ) )
self . use_encryption = config . get ( ' use_encryption ' , False )
self . seed = config . get ( ' seed ' , ' ' ) # encrypted
2013-01-23 23:11:48 -08:00
self . labels = config . get ( ' labels ' , { } )
2012-10-11 11:10:12 -07:00
self . frozen_addresses = config . get ( ' frozen_addresses ' , [ ] )
self . prioritized_addresses = config . get ( ' prioritized_addresses ' , [ ] )
2013-01-23 23:11:48 -08:00
self . addressbook = config . get ( ' contacts ' , [ ] )
2013-06-17 13:09:28 -07:00
2012-10-11 11:10:12 -07:00
self . imported_keys = config . get ( ' imported_keys ' , { } )
2012-11-04 06:47:19 -08:00
self . history = config . get ( ' addr_history ' , { } ) # address -> list(txid, height)
2013-02-27 00:04:22 -08:00
2013-03-02 14:31:40 -08:00
2013-08-01 11:08:56 -07:00
self . master_public_keys = config . get ( ' master_public_keys ' , { } )
self . master_private_keys = config . get ( ' master_private_keys ' , { } )
self . load_accounts ( config )
2012-01-19 08:11:36 -08:00
2013-02-22 10:22:22 -08:00
self . transactions = { }
tx = config . get ( ' transactions ' , { } )
try :
for k , v in tx . items ( ) : self . transactions [ k ] = Transaction ( v )
except :
print_msg ( " Warning: Cannot deserialize transactions. skipping " )
2012-01-19 08:11:36 -08:00
# not saved
2012-11-16 05:39:31 -08:00
self . prevout_values = { } # my own transaction outputs
2012-11-03 01:17:40 -07:00
self . spent_outputs = [ ]
2012-05-04 02:14:07 -07:00
2012-10-26 01:02:09 -07:00
# spv
self . verifier = None
2012-10-14 22:43:00 -07:00
# there is a difference between wallet.up_to_date and interface.is_up_to_date()
# interface.is_up_to_date() returns true when all requests have been answered and processed
# wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
2012-11-20 12:36:06 -08:00
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 ( )
2013-03-23 23:34:28 -07:00
self . transaction_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-10-11 11:10:12 -07:00
if self . seed_version != SEED_VERSION :
raise ValueError ( " This wallet seed is deprecated. Please run upgrade.py for a diagnostic. " )
2013-06-01 10:26:07 -07:00
for tx_hash , tx in self . transactions . items ( ) :
if self . check_new_tx ( tx_hash , tx ) :
self . update_tx_outputs ( tx_hash )
else :
print_error ( " unreferenced tx " , tx_hash )
self . transactions . pop ( tx_hash )
2012-11-03 01:17:40 -07:00
2012-11-20 12:36:06 -08:00
def set_up_to_date ( self , b ) :
with self . lock : self . up_to_date = b
def is_up_to_date ( self ) :
with self . lock : return self . up_to_date
2012-04-01 08:50:12 -07:00
2012-11-22 04:10:01 -08:00
def update ( self ) :
self . up_to_date = False
self . interface . poke ( ' synchronizer ' )
2012-11-22 11:14:42 -08:00
while not self . is_up_to_date ( ) : time . sleep ( 0.1 )
2012-11-03 01:17:40 -07:00
2013-01-05 12:28:12 -08:00
def import_key ( self , sec , password ) :
2013-02-23 02:35:46 -08:00
# check password
seed = self . decode_seed ( password )
2013-05-05 08:38:59 -07:00
try :
address = address_from_private_key ( sec )
except :
raise BaseException ( ' Invalid private key ' )
2013-02-21 07:26:26 -08:00
2013-03-02 02:40:17 -08:00
if self . is_mine ( address ) :
2013-01-05 12:28:12 -08:00
raise BaseException ( ' Address already in wallet ' )
2012-12-31 01:41:02 -08:00
# store the originally requested keypair into the imported keys table
2013-02-23 02:35:46 -08:00
self . imported_keys [ address ] = pw_encode ( sec , password )
2013-04-29 01:30:29 -07:00
self . config . set_key ( ' imported_keys ' , self . imported_keys , True )
2013-01-05 12:28:12 -08:00
return address
2012-12-31 01:41:02 -08:00
2013-05-02 00:54:43 -07:00
def delete_imported_key ( self , addr ) :
if addr in self . imported_keys :
self . imported_keys . pop ( addr )
self . config . set_key ( ' imported_keys ' , self . imported_keys , True )
2012-10-11 11:10:12 -07:00
2013-02-03 06:08:26 -08:00
def init_seed ( self , seed ) :
2013-02-03 06:12:40 -08:00
if self . seed : raise BaseException ( " a seed exists " )
2013-02-03 06:08:26 -08:00
if not seed :
2013-02-24 01:16:14 -08:00
seed = random_seed ( 128 )
2013-04-09 09:08:14 -07:00
self . seed = seed
2013-08-01 11:08:56 -07:00
2013-04-09 09:08:14 -07:00
def save_seed ( self ) :
2013-02-03 06:08:26 -08:00
self . config . set_key ( ' seed ' , self . seed , True )
self . config . set_key ( ' seed_version ' , self . seed_version , True )
2013-08-01 11:08:56 -07:00
master_k , master_c , master_K , master_cK = bip32_init ( self . seed )
2013-08-15 08:23:55 -07:00
# normal accounts
2013-08-01 11:08:56 -07:00
k0 , c0 , K0 , cK0 = bip32_private_derivation ( master_k , master_c , " m/ " , " m/0 ' / " )
2013-08-15 08:23:55 -07:00
# p2sh 2of2
2013-08-01 11:08:56 -07:00
k1 , c1 , K1 , cK1 = bip32_private_derivation ( master_k , master_c , " m/ " , " m/1 ' / " )
k2 , c2 , K2 , cK2 = bip32_private_derivation ( master_k , master_c , " m/ " , " m/2 ' / " )
2013-08-15 08:23:55 -07:00
# p2sh 2of3
2013-08-15 06:27:03 -07:00
k3 , c3 , K3 , cK3 = bip32_private_derivation ( master_k , master_c , " m/ " , " m/3 ' / " )
k4 , c4 , K4 , cK4 = bip32_private_derivation ( master_k , master_c , " m/ " , " m/4 ' / " )
k5 , c5 , K5 , cK5 = bip32_private_derivation ( master_k , master_c , " m/ " , " m/5 ' / " )
2013-08-01 11:08:56 -07:00
self . master_public_keys = {
" m/0 ' / " : ( c0 , K0 , cK0 ) ,
" m/1 ' / " : ( c1 , K1 , cK1 ) ,
2013-08-15 06:27:03 -07:00
" m/2 ' / " : ( c2 , K2 , cK2 ) ,
" m/3 ' / " : ( c3 , K3 , cK3 ) ,
" m/4 ' / " : ( c4 , K4 , cK4 ) ,
" m/5 ' / " : ( c5 , K5 , cK5 )
2013-08-01 11:08:56 -07:00
}
self . master_private_keys = {
" m/0 ' / " : k0 ,
2013-08-15 06:27:03 -07:00
" m/1 ' / " : k1 ,
" m/2 ' / " : k2 ,
" m/3 ' / " : k3 ,
" m/4 ' / " : k4 ,
" m/5 ' / " : k5
2013-08-01 11:08:56 -07:00
}
self . config . set_key ( ' master_public_keys ' , self . master_public_keys , True )
self . config . set_key ( ' master_private_keys ' , self . master_private_keys , True )
# create default account
2013-08-15 08:23:55 -07:00
self . create_account ( ' Main account ' )
2013-08-01 11:08:56 -07:00
2013-08-16 04:26:48 -07:00
def account_id ( self , account_type , i ) :
2013-08-15 08:23:55 -07:00
if account_type is None :
2013-08-16 04:26:48 -07:00
return " m/0 ' / %d ' " % i
2013-08-15 08:23:55 -07:00
elif account_type == ' 2of2 ' :
2013-08-16 04:26:48 -07:00
return " m/1 ' / %d & m/2 ' / %d " % ( i , i )
2013-08-15 08:23:55 -07:00
elif account_type == ' 2of3 ' :
2013-08-16 04:26:48 -07:00
return " m/3 ' / %d & m/4 ' / %d & m/5 ' / %d " % ( i , i , i )
2013-08-15 08:23:55 -07:00
else :
raise BaseException ( ' unknown account type ' )
2013-08-01 11:08:56 -07:00
2013-08-16 04:26:48 -07:00
def num_accounts ( self , account_type ) :
2013-08-01 11:08:56 -07:00
keys = self . accounts . keys ( )
i = 0
while True :
2013-08-16 04:26:48 -07:00
account_id = self . account_id ( account_type , i )
2013-08-01 11:08:56 -07:00
if account_id not in keys : break
i + = 1
2013-08-16 04:26:48 -07:00
return i
def create_account ( self , name , account_type = None ) :
i = self . num_accounts ( account_type )
acount_id = self . account_id ( account_type , i )
2013-08-01 11:08:56 -07:00
2013-08-15 08:23:55 -07:00
if account_type is None :
master_c0 , master_K0 , _ = self . master_public_keys [ " m/0 ' / " ]
c0 , K0 , cK0 = bip32_public_derivation ( master_c0 . decode ( ' hex ' ) , master_K0 . decode ( ' hex ' ) , " m/0 ' / " , " m/0 ' / %d " % i )
account = BIP32_Account ( { ' name ' : name , ' c ' : c0 , ' K ' : K0 , ' cK ' : cK0 } )
elif account_type == ' 2of2 ' :
master_c1 , master_K1 , _ = self . master_public_keys [ " m/1 ' / " ]
c1 , K1 , cK1 = bip32_public_derivation ( master_c1 . decode ( ' hex ' ) , master_K1 . decode ( ' hex ' ) , " m/1 ' / " , " m/1 ' / %d " % i )
master_c2 , master_K2 , _ = self . master_public_keys [ " m/2 ' / " ]
c2 , K2 , cK2 = bip32_public_derivation ( master_c2 . decode ( ' hex ' ) , master_K2 . decode ( ' hex ' ) , " m/2 ' / " , " m/2 ' / %d " % i )
account = BIP32_Account_2of2 ( { ' name ' : name , ' c ' : c1 , ' K ' : K1 , ' cK ' : cK1 , ' c2 ' : c2 , ' K2 ' : K2 , ' cK2 ' : cK2 } )
elif account_type == ' 2of3 ' :
master_c3 , master_K3 , _ = self . master_public_keys [ " m/3 ' / " ]
c3 , K3 , cK3 = bip32_public_derivation ( master_c3 . decode ( ' hex ' ) , master_K3 . decode ( ' hex ' ) , " m/3 ' / " , " m/3 ' / %d " % i )
master_c4 , master_K4 , _ = self . master_public_keys [ " m/4 ' / " ]
c4 , K4 , cK4 = bip32_public_derivation ( master_c4 . decode ( ' hex ' ) , master_K4 . decode ( ' hex ' ) , " m/4 ' / " , " m/4 ' / %d " % i )
master_c5 , master_K5 , _ = self . master_public_keys [ " m/5 ' / " ]
c5 , K5 , cK5 = bip32_public_derivation ( master_c5 . decode ( ' hex ' ) , master_K5 . decode ( ' hex ' ) , " m/5 ' / " , " m/5 ' / %d " % i )
account = BIP32_Account_2of3 ( { ' name ' : name , ' c ' : c3 , ' K ' : K3 , ' cK ' : cK3 , ' c2 ' : c4 , ' K2 ' : K4 , ' cK2 ' : cK4 , ' c3 ' : c5 , ' K3 ' : K5 , ' cK3 ' : cK5 } )
self . accounts [ account_id ] = account
2013-08-01 11:08:56 -07:00
self . save_accounts ( )
def save_accounts ( self ) :
d = { }
for k , v in self . accounts . items ( ) :
d [ k ] = v . dump ( )
self . config . set_key ( ' accounts ' , d , True )
def load_accounts ( self , config ) :
d = config . get ( ' accounts ' , { } )
self . accounts = { }
for k , v in d . items ( ) :
if ' & ' in k :
self . accounts [ k ] = BIP32_Account_2of2 ( v )
else :
self . accounts [ k ] = BIP32_Account ( v )
2013-03-07 07:41:43 -08:00
2013-02-27 00:04:22 -08:00
2013-04-05 07:00:34 -07:00
def addresses ( self , include_change = True ) :
o = self . get_account_addresses ( - 1 , include_change )
for a in self . accounts . keys ( ) :
o + = self . get_account_addresses ( a , include_change )
2013-02-27 00:04:22 -08:00
return o
2012-01-19 08:11:36 -08:00
def is_mine ( self , address ) :
2013-03-02 02:40:17 -08:00
return address in self . addresses ( True )
2012-01-19 08:11:36 -08:00
def is_change ( self , address ) :
2013-03-17 13:13:10 -07:00
if not self . is_mine ( address ) : return False
if address in self . imported_keys . keys ( ) : return False
2013-03-16 09:51:58 -07:00
acct , s = self . get_address_index ( address )
return s [ 0 ] == 1
2012-01-19 08:11:36 -08:00
2013-02-23 02:35:46 -08:00
def get_master_public_key ( self ) :
2013-08-01 11:08:56 -07:00
raise
2013-03-16 15:38:49 -07:00
return self . config . get ( " master_public_key " )
2013-02-23 02:35:46 -08:00
2013-08-05 04:53:50 -07:00
def get_master_private_key ( self , account , password ) :
master_k = pw_decode ( self . master_private_keys [ account ] , password )
master_c , master_K , master_Kc = self . master_public_keys [ account ]
try :
K , Kc = get_pubkeys_from_secret ( master_k . decode ( ' hex ' ) )
assert K . encode ( ' hex ' ) == master_K
except :
raise BaseException ( " Invalid password " )
return master_k
2013-02-25 00:05:45 -08:00
def get_address_index ( self , address ) :
2013-02-23 02:35:46 -08:00
if address in self . imported_keys . keys ( ) :
2013-04-08 08:02:28 -07:00
return - 1 , None
2013-02-27 00:04:22 -08:00
for account in self . accounts . keys ( ) :
for for_change in [ 0 , 1 ] :
2013-08-01 11:08:56 -07:00
addresses = self . accounts [ account ] . get_addresses ( for_change )
2013-02-27 00:04:22 -08:00
for addr in addresses :
if address == addr :
2013-03-03 01:24:30 -08:00
return account , ( for_change , addresses . index ( addr ) )
2013-02-27 00:04:22 -08:00
raise BaseException ( " not found " )
2012-01-19 08:11:36 -08:00
2013-02-25 00:05:45 -08:00
def get_public_key ( self , address ) :
2013-03-03 01:24:30 -08:00
account , sequence = self . get_address_index ( address )
2013-08-07 11:04:43 -07:00
return self . accounts [ account ] . get_pubkey ( * sequence )
2012-01-19 08:11:36 -08:00
2013-02-23 02:35:46 -08:00
def decode_seed ( self , password ) :
seed = pw_decode ( self . seed , password )
2013-08-01 11:08:56 -07:00
#todo: #self.sequences[0].check_seed(seed)
2013-02-23 02:35:46 -08:00
return seed
2013-08-05 04:53:50 -07:00
2012-02-06 09:10:30 -08:00
def get_private_key ( self , address , password ) :
2013-08-16 03:17:29 -07:00
out = [ ]
2013-08-05 04:53:50 -07:00
if address in self . imported_keys . keys ( ) :
2013-08-16 03:17:29 -07:00
out . append ( pw_decode ( self . imported_keys [ address ] , password ) )
2013-08-05 04:53:50 -07:00
else :
account , sequence = self . get_address_index ( address )
2013-08-16 03:17:29 -07:00
# assert address == self.accounts[account].get_address(*sequence)
l = account . split ( " & " )
for s in l :
s = s . strip ( )
m = re . match ( " (m/ \ d+ ' /)( \ d+) " , s )
if m :
root = m . group ( 1 )
if root not in self . master_private_keys . keys ( ) : continue
num = int ( m . group ( 2 ) )
master_k = self . get_master_private_key ( root , password )
master_c , _ , _ = self . master_public_keys [ root ]
pk = bip32_private_key ( ( num , ) + sequence , master_k . decode ( ' hex ' ) , master_c . decode ( ' hex ' ) )
out . append ( pk )
return out
2013-08-05 04:53:50 -07:00
2013-02-22 08:27:19 -08:00
2013-02-27 06:49:26 -08:00
2013-02-27 07:15:56 -08:00
def signrawtransaction ( self , tx , input_info , private_keys , password ) :
2013-08-17 00:53:46 -07:00
import deserialize
2013-02-27 06:49:26 -08:00
unspent_coins = self . get_unspent_coins ( )
2013-02-27 07:15:56 -08:00
seed = self . decode_seed ( password )
2013-02-27 06:49:26 -08:00
2013-08-17 00:53:46 -07:00
# build a list of public/private keys
keypairs = { }
2013-02-27 06:49:26 -08:00
for sec in private_keys :
2013-08-17 00:53:46 -07:00
compressed = is_compressed ( sec )
pkey = regenerate_key ( sec )
pubkey = GetPubKey ( pkey . pubkey , compressed )
keypairs [ pubkey . encode ( ' hex ' ) ] = sec
2013-02-27 06:49:26 -08:00
for txin in tx . inputs :
# convert to own format
txin [ ' tx_hash ' ] = txin [ ' prevout_hash ' ]
txin [ ' index ' ] = txin [ ' prevout_n ' ]
for item in input_info :
if item . get ( ' txid ' ) == txin [ ' tx_hash ' ] and item . get ( ' vout ' ) == txin [ ' index ' ] :
txin [ ' raw_output_script ' ] = item [ ' scriptPubKey ' ]
txin [ ' redeemScript ' ] = item . get ( ' redeemScript ' )
2013-03-12 09:54:26 -07:00
txin [ ' KeyID ' ] = item . get ( ' KeyID ' )
2013-02-27 06:49:26 -08:00
break
else :
for item in unspent_coins :
if txin [ ' tx_hash ' ] == item [ ' tx_hash ' ] and txin [ ' index ' ] == item [ ' index ' ] :
txin [ ' raw_output_script ' ] = item [ ' raw_output_script ' ]
break
else :
# if neither, we might want to get it from the server..
raise
2013-08-17 00:53:46 -07:00
# find the address and fill private_keys
2013-03-12 09:54:26 -07:00
if txin . get ( ' KeyID ' ) :
account , name , sequence = txin . get ( ' KeyID ' )
2013-08-17 00:53:46 -07:00
if name != ' BIP32 ' : continue
2013-08-01 11:08:56 -07:00
sec = self . accounts [ account ] . get_private_key ( sequence , seed )
2013-08-17 01:56:23 -07:00
pubkey = self . accounts [ account ] . get_pubkey ( sequence )
2013-02-27 00:04:22 -08:00
txin [ ' address ' ] = addr
2013-08-17 01:56:23 -07:00
keypairs [ pubkey ] = [ sec ]
2013-02-27 06:49:26 -08:00
2013-08-17 00:53:46 -07:00
redeem_script = txin . get ( " redeemScript " )
if redeem_script :
num , redeem_pubkeys = deserialize . parse_redeemScript ( redeem_script )
addr = hash_160_to_bc_address ( hash_160 ( redeem_script . decode ( ' hex ' ) ) , 5 )
txin [ ' address ' ] = addr
2013-08-17 01:56:23 -07:00
2013-02-27 06:49:26 -08:00
elif txin . get ( " raw_output_script " ) :
addr = deserialize . get_address_from_output_script ( txin . get ( " raw_output_script " ) . decode ( ' hex ' ) )
2013-02-27 07:15:56 -08:00
sec = self . get_private_key ( addr , password )
2013-02-27 06:49:26 -08:00
if sec :
2013-08-17 01:56:23 -07:00
keypairs [ pubkey ] = [ sec ]
2013-02-27 06:49:26 -08:00
txin [ ' address ' ] = addr
2013-08-17 01:56:23 -07:00
tx . sign ( keypairs )
2012-01-19 08:11:36 -08:00
2012-02-01 11:27:03 -08:00
def sign_message ( self , address , message , password ) :
2013-02-22 08:27:19 -08:00
sec = self . get_private_key ( address , password )
2013-02-22 07:17:46 -08:00
key = regenerate_key ( sec )
compressed = is_compressed ( sec )
return key . sign_message ( message , compressed , address )
2013-02-27 00:04:22 -08:00
2013-05-01 01:40:44 -07:00
def verify_message ( self , address , signature , message ) :
try :
EC_KEY . verify_message ( address , signature , message )
return True
except BaseException as e :
print_error ( " Verification error: {0} " . format ( e ) )
return False
2013-02-27 00:04:22 -08:00
2012-02-21 05:36:45 -08:00
2012-06-07 09:52:29 -07:00
def change_gap_limit ( self , value ) :
if value > = self . gap_limit :
self . gap_limit = value
2013-05-02 00:54:43 -07:00
self . config . set_key ( ' gap_limit ' , self . gap_limit , True )
2012-10-26 15:02:52 -07:00
self . interface . poke ( ' synchronizer ' )
2012-06-07 09:52:29 -07:00
return True
elif value > = self . min_acceptable_gap ( ) :
2013-02-27 00:04:22 -08:00
for key , account in self . accounts . items ( ) :
addresses = account [ 0 ]
k = self . num_unused_trailing_addresses ( addresses )
n = len ( addresses ) - k + value
addresses = addresses [ 0 : n ]
self . accounts [ key ] [ 0 ] = addresses
2012-06-07 09:52:29 -07:00
self . gap_limit = value
2013-05-02 01:54:48 -07:00
self . config . set_key ( ' gap_limit ' , self . gap_limit , True )
2013-08-01 11:08:56 -07:00
self . save_accounts ( )
2012-06-07 09:52:29 -07:00
return True
else :
return False
2013-02-27 00:04:22 -08:00
def num_unused_trailing_addresses ( self , addresses ) :
2012-06-07 09:52:29 -07:00
k = 0
2013-02-27 00:04:22 -08:00
for a in addresses [ : : - 1 ] :
2012-06-07 09:52:29 -07:00
if self . history . get ( a ) : break
k = k + 1
return k
def min_acceptable_gap ( self ) :
# fixme: this assumes wallet is synchronized
n = 0
nmax = 0
2013-02-27 00:04:22 -08:00
for account in self . accounts . values ( ) :
2013-08-01 11:08:56 -07:00
addresses = account . get_addresses ( 0 )
2013-02-27 00:04:22 -08:00
k = self . num_unused_trailing_addresses ( addresses )
for a in addresses [ 0 : - k ] :
if self . history . get ( a ) :
n = 0
else :
n + = 1
if n > nmax : nmax = n
2012-06-07 09:59:28 -07:00
return nmax + 1
2012-06-07 09:52:29 -07:00
2012-01-19 08:11:36 -08:00
2013-01-29 05:53:13 -08:00
def address_is_old ( self , address ) :
age = - 1
h = self . history . get ( address , [ ] )
if h == [ ' * ' ] :
return True
for tx_hash , tx_height in h :
if tx_height == 0 :
tx_age = 0
else :
tx_age = self . verifier . height - tx_height + 1
if tx_age > age :
age = tx_age
return age > 2
2012-02-22 06:39:06 -08:00
2012-01-19 08:11:36 -08:00
2013-02-27 00:04:22 -08:00
def synchronize_sequence ( self , account , for_change ) :
limit = self . gap_limit_for_change if for_change else self . gap_limit
2013-01-29 05:53:13 -08:00
new_addresses = [ ]
2012-01-19 08:11:36 -08:00
while True :
2013-08-01 11:08:56 -07:00
addresses = account . get_addresses ( for_change )
2013-02-27 00:04:22 -08:00
if len ( addresses ) < limit :
2013-08-01 11:08:56 -07:00
address = account . create_new_address ( for_change )
self . history [ address ] = [ ]
new_addresses . append ( address )
2012-01-19 08:11:36 -08:00
continue
2013-08-01 11:08:56 -07:00
2013-02-27 00:04:22 -08:00
if map ( lambda a : self . address_is_old ( a ) , addresses [ - limit : ] ) == limit * [ False ] :
2012-01-19 08:11:36 -08:00
break
else :
2013-08-01 11:08:56 -07:00
address = account . create_new_address ( for_change )
self . history [ address ] = [ ]
new_addresses . append ( address )
2013-01-29 05:53:13 -08:00
return new_addresses
2012-01-19 08:11:36 -08:00
2013-02-27 00:04:22 -08:00
def synchronize_account ( self , account ) :
new = [ ]
new + = self . synchronize_sequence ( account , 0 )
new + = self . synchronize_sequence ( account , 1 )
return new
2013-01-29 05:53:13 -08:00
def synchronize ( self ) :
2013-02-27 00:04:22 -08:00
new = [ ]
2013-08-01 11:08:56 -07:00
for account in self . accounts . values ( ) :
2013-02-27 00:04:22 -08:00
new + = self . synchronize_account ( account )
2013-05-02 01:54:48 -07:00
if new :
2013-08-01 11:08:56 -07:00
self . save_accounts ( )
2013-05-02 01:54:48 -07:00
self . config . set_key ( ' addr_history ' , self . history , True )
2013-02-27 00:04:22 -08:00
return new
2012-02-22 06:39:06 -08:00
2012-03-23 08:34:34 -07:00
2012-01-19 08:11:36 -08:00
def is_found ( self ) :
2013-02-27 01:13:03 -08:00
return self . history . values ( ) != [ [ ] ] * len ( self . history )
2012-01-19 08:11:36 -08:00
2013-05-02 01:10:22 -07:00
def add_contact ( self , address , label = None ) :
self . addressbook . append ( address )
2013-06-17 13:09:28 -07:00
self . config . set_key ( ' contacts ' , self . addressbook , True )
2013-05-02 01:10:22 -07:00
if label :
self . labels [ address ] = label
2013-05-02 01:19:18 -07:00
self . config . set_key ( ' labels ' , self . labels )
def delete_contact ( self , addr ) :
if addr in self . addressbook :
self . addressbook . remove ( addr )
self . config . set_key ( ' addressbook ' , self . addressbook , True )
2013-05-02 01:10:22 -07:00
2012-01-19 08:11:36 -08:00
def fill_addressbook ( self ) :
2012-11-03 01:17:40 -07:00
for tx_hash , tx in self . transactions . items ( ) :
2013-04-05 07:00:34 -07:00
is_relevant , is_send , _ , _ = self . get_tx_value ( tx )
2012-11-16 05:39:31 -08:00
if is_send :
2013-02-25 10:04:41 -08:00
for addr , v in tx . outputs :
2012-11-04 11:53:27 -08:00
if not self . is_mine ( addr ) and addr not in self . addressbook :
self . addressbook . append ( addr )
2012-01-19 08:11:36 -08:00
# redo labels
2012-11-04 11:53:27 -08:00
# self.update_tx_labels()
2012-01-19 08:11:36 -08:00
2013-03-16 10:17:50 -07:00
def get_num_tx ( self , address ) :
n = 0
for tx in self . transactions . values ( ) :
if address in map ( lambda x : x [ 0 ] , tx . outputs ) : n + = 1
return n
2012-01-19 08:11:36 -08: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
2013-04-05 07:00:34 -07:00
def get_tx_value ( self , tx , account = None ) :
domain = self . get_account_addresses ( account )
return tx . get_value ( domain , self . prevout_values )
2012-11-16 05:39:31 -08:00
2012-11-03 01:17:40 -07:00
def update_tx_outputs ( self , tx_hash ) :
tx = self . transactions . get ( tx_hash )
2013-03-24 04:20:13 -07:00
for i , ( addr , value ) in enumerate ( tx . outputs ) :
2013-02-22 10:22:22 -08:00
key = tx_hash + ' : %d ' % i
2013-03-23 23:34:28 -07:00
self . prevout_values [ key ] = value
2012-11-03 01:17:40 -07:00
2013-02-22 10:22:22 -08:00
for item in tx . inputs :
2012-11-03 01:17:40 -07:00
if self . is_mine ( item . get ( ' address ' ) ) :
key = item [ ' prevout_hash ' ] + ' : %d ' % item [ ' prevout_n ' ]
self . spent_outputs . append ( key )
2012-11-22 04:10:01 -08:00
def get_addr_balance ( self , address ) :
assert self . is_mine ( address )
h = self . history . get ( address , [ ] )
2012-11-14 06:33:44 -08:00
if h == [ ' * ' ] : return 0 , 0
2012-01-19 08:11:36 -08:00
c = u = 0
2012-12-28 08:57:33 -08:00
received_coins = [ ] # list of coins received at address
2012-11-22 04:24:44 -08:00
for tx_hash , tx_height in h :
2013-02-22 10:22:22 -08:00
tx = self . transactions . get ( tx_hash )
if not tx : continue
2013-03-24 04:20:13 -07:00
for i , ( addr , value ) in enumerate ( tx . outputs ) :
2012-12-28 08:57:33 -08:00
if addr == address :
2013-02-22 10:22:22 -08:00
key = tx_hash + ' : %d ' % i
2012-12-28 08:57:33 -08:00
received_coins . append ( key )
2012-11-22 04:24:44 -08:00
2012-11-03 01:17:40 -07:00
for tx_hash , tx_height in h :
2013-02-22 10:22:22 -08:00
tx = self . transactions . get ( tx_hash )
if not tx : continue
2012-11-22 04:10:01 -08:00
v = 0
2013-02-22 10:22:22 -08:00
for item in tx . inputs :
2012-11-22 04:10:01 -08:00
addr = item . get ( ' address ' )
if addr == address :
key = item [ ' prevout_hash ' ] + ' : %d ' % item [ ' prevout_n ' ]
value = self . prevout_values . get ( key )
if key in received_coins :
v - = value
2013-02-22 10:22:22 -08:00
2013-03-24 04:20:13 -07:00
for i , ( addr , value ) in enumerate ( tx . outputs ) :
2013-02-22 10:22:22 -08:00
key = tx_hash + ' : %d ' % i
2012-11-22 04:10:01 -08:00
if addr == address :
2013-02-22 10:22:22 -08:00
v + = value
2012-11-16 05:39:31 -08:00
2012-11-03 01:17:40 -07:00
if tx_height :
2012-01-19 08:11:36 -08:00
c + = v
else :
u + = v
return c , u
2013-04-05 07:00:34 -07:00
def get_accounts ( self ) :
accounts = { }
for k , account in self . accounts . items ( ) :
2013-08-01 11:08:56 -07:00
accounts [ k ] = account . name
2013-04-05 07:00:34 -07:00
if self . imported_keys :
accounts [ - 1 ] = ' Imported keys '
return accounts
def get_account_addresses ( self , a , include_change = True ) :
if a is None :
o = self . addresses ( True )
elif a == - 1 :
o = self . imported_keys . keys ( )
else :
ac = self . accounts [ a ]
2013-08-01 11:08:56 -07:00
o = ac . get_addresses ( 0 )
if include_change : o + = ac . get_addresses ( 1 )
2013-04-05 07:00:34 -07:00
return o
2013-03-02 05:20:21 -08:00
def get_imported_balance ( self ) :
cc = uu = 0
for addr in self . imported_keys . keys ( ) :
c , u = self . get_addr_balance ( addr )
cc + = c
uu + = u
return cc , uu
2013-02-27 00:04:22 -08:00
def get_account_balance ( self , account ) :
2013-04-05 07:00:34 -07:00
if account is None :
return self . get_balance ( )
elif account == - 1 :
return self . get_imported_balance ( )
2012-01-19 08:11:36 -08:00
conf = unconf = 0
2013-02-27 00:04:22 -08:00
for addr in self . get_account_addresses ( account ) :
2012-01-19 08:11:36 -08:00
c , u = self . get_addr_balance ( addr )
conf + = c
unconf + = u
return conf , unconf
2013-04-12 05:29:11 -07:00
def get_frozen_balance ( self ) :
conf = unconf = 0
for addr in self . frozen_addresses :
c , u = self . get_addr_balance ( addr )
conf + = c
unconf + = u
return conf , unconf
2013-02-27 00:04:22 -08:00
def get_balance ( self ) :
cc = uu = 0
for a in self . accounts . keys ( ) :
c , u = self . get_account_balance ( a )
cc + = c
uu + = u
2013-03-02 05:20:21 -08:00
c , u = self . get_imported_balance ( )
cc + = c
uu + = u
2013-02-27 00:04:22 -08:00
return cc , uu
2012-01-19 08:11:36 -08:00
2013-02-23 04:18:15 -08:00
def get_unspent_coins ( self , domain = None ) :
2012-01-19 08:11:36 -08:00
coins = [ ]
2013-03-02 02:40:17 -08:00
if domain is None : domain = self . addresses ( True )
2012-02-10 04:23:39 -08:00
for addr in domain :
2012-11-03 01:17:40 -07:00
h = self . history . get ( addr , [ ] )
2012-11-15 00:14:24 -08:00
if h == [ ' * ' ] : continue
2012-11-15 03:14:29 -08:00
for tx_hash , tx_height in h :
2012-11-03 01:17:40 -07:00
tx = self . transactions . get ( tx_hash )
2013-03-12 15:10:43 -07:00
if tx is None : raise BaseException ( " Wallet not synchronized " )
2013-02-23 03:07:46 -08:00
for output in tx . d . get ( ' outputs ' ) :
2012-11-03 01:17:40 -07:00
if output . get ( ' address ' ) != addr : continue
key = tx_hash + " : %d " % output . get ( ' index ' )
if key in self . spent_outputs : continue
output [ ' tx_hash ' ] = tx_hash
coins . append ( output )
2013-02-23 04:18:15 -08:00
return coins
2012-11-03 01:17:40 -07:00
2012-06-06 06:40:57 -07:00
2013-04-05 07:00:34 -07:00
def choose_tx_inputs ( self , amount , fixed_fee , account = None ) :
2013-02-23 04:18:15 -08:00
""" todo: minimize tx size """
total = 0
fee = self . fee if fixed_fee is None else fixed_fee
2013-04-05 07:00:34 -07:00
domain = self . get_account_addresses ( account )
2013-02-23 04:18:15 -08:00
coins = [ ]
prioritized_coins = [ ]
for i in self . frozen_addresses :
if i in domain : domain . remove ( i )
for i in self . prioritized_addresses :
if i in domain : domain . remove ( i )
coins = self . get_unspent_coins ( domain )
prioritized_coins = self . get_unspent_coins ( self . prioritized_addresses )
2012-06-06 06:40:57 -07:00
2012-01-19 08:11:36 -08:00
inputs = [ ]
2012-06-06 06:40:57 -07:00
coins = prioritized_coins + coins
2012-11-03 01:17:40 -07:00
for item in coins :
addr = item . get ( ' address ' )
2012-01-19 08:11:36 -08:00
v = item . get ( ' value ' )
total + = v
2013-02-21 05:18:12 -08:00
inputs . append ( item )
2013-04-05 09:03:52 -07:00
fee = self . estimated_fee ( inputs ) if fixed_fee is None else fixed_fee
2012-01-19 08:11:36 -08:00
if total > = amount + fee : break
else :
inputs = [ ]
2013-03-03 01:24:30 -08:00
2012-01-19 08:11:36 -08:00
return inputs , total , fee
2013-03-23 01:23:57 -07:00
2013-04-05 09:03:52 -07:00
def estimated_fee ( self , inputs ) :
estimated_size = len ( inputs ) * 180 + 80 # this assumes non-compressed keys
fee = self . fee * int ( round ( estimated_size / 1024. ) )
if fee == 0 : fee = self . fee
return fee
2013-03-23 01:23:57 -07:00
2013-04-05 07:00:34 -07:00
def add_tx_change ( self , inputs , outputs , amount , fee , total , change_addr = None , account = 0 ) :
" add change to a transaction "
2012-01-19 08:11:36 -08:00
change_amount = total - ( amount + fee )
if change_amount != 0 :
2012-02-07 19:22:18 -08:00
if not change_addr :
2013-04-08 07:18:40 -07:00
if account is None :
# send change to one of the accounts involved in the tx
address = inputs [ 0 ] . get ( ' address ' )
account , _ = self . get_address_index ( address )
2013-04-05 07:00:34 -07:00
if not self . use_change or account == - 1 :
change_addr = inputs [ - 1 ] [ ' address ' ]
else :
2013-08-05 04:53:50 -07:00
change_addr = self . accounts [ account ] . get_addresses ( 1 ) [ - self . gap_limit_for_change ]
2013-04-05 07:00:34 -07:00
2013-01-22 01:29:37 -08:00
# Insert the change output at a random position in the outputs
posn = random . randint ( 0 , len ( outputs ) )
outputs [ posn : posn ] = [ ( change_addr , change_amount ) ]
2012-01-19 08:11:36 -08:00
return outputs
2012-11-07 00:37:14 -08:00
def get_history ( self , address ) :
2012-10-20 17:57:31 -07:00
with self . lock :
2012-11-07 00:37:14 -08:00
return self . history . get ( address )
2013-04-05 07:00:34 -07:00
2012-11-07 00:37:14 -08:00
def get_status ( self , h ) :
2012-11-03 01:17:40 -07:00
if not h : return None
2012-11-14 06:33:44 -08:00
if h == [ ' * ' ] : return ' * '
2012-11-03 01:17:40 -07:00
status = ' '
for tx_hash , height in h :
status + = tx_hash + ' : %d : ' % height
return hashlib . sha256 ( status ) . digest ( ) . encode ( ' hex ' )
2012-03-30 05:15:05 -07:00
2013-02-22 10:22:22 -08:00
def receive_tx_callback ( self , tx_hash , tx , tx_height ) :
2012-11-07 11:25:23 -08:00
if not self . check_new_tx ( tx_hash , tx ) :
2013-03-12 13:55:56 -07:00
# may happen due to pruning
print_error ( " received transaction that is no longer referenced in history " , tx_hash )
return
2012-11-07 11:25:23 -08:00
2013-03-23 23:34:28 -07:00
with self . transaction_lock :
2012-11-07 11:25:23 -08:00
self . transactions [ tx_hash ] = tx
2013-05-31 09:23:51 -07:00
2013-06-17 06:12:20 -07:00
self . interface . pending_transactions_for_notifications . append ( tx )
2013-05-31 09:23:51 -07:00
2013-05-02 01:54:48 -07:00
self . save_transactions ( )
2013-03-23 23:34:28 -07:00
if self . verifier and tx_height > 0 :
self . verifier . add ( tx_hash , tx_height )
self . update_tx_outputs ( tx_hash )
2012-11-03 01:17:40 -07:00
2013-05-02 01:54:48 -07:00
def save_transactions ( self ) :
tx = { }
for k , v in self . transactions . items ( ) :
tx [ k ] = str ( v )
self . config . set_key ( ' transactions ' , tx , True )
2012-11-03 01:17:40 -07:00
def receive_history_callback ( self , addr , hist ) :
2012-11-07 11:25:23 -08:00
2012-11-23 06:35:54 -08:00
if not self . check_new_history ( addr , hist ) :
raise BaseException ( " error: received history for %s is not consistent with known transactions " % addr )
2012-11-07 11:25:23 -08:00
2012-11-03 01:17:40 -07:00
with self . lock :
self . history [ addr ] = hist
2013-05-02 01:56:28 -07:00
self . config . set_key ( ' addr_history ' , self . history , True )
2012-11-14 06:33:44 -08:00
if hist != [ ' * ' ] :
2012-11-05 11:40:57 -08:00
for tx_hash , tx_height in hist :
if tx_height > 0 :
2012-11-15 03:14:29 -08:00
# add it in case it was previously unconfirmed
2012-11-18 02:34:52 -08:00
if self . verifier : self . verifier . add ( tx_hash , tx_height )
2012-11-03 01:17:40 -07:00
2012-11-07 11:25:23 -08:00
2013-04-05 07:00:34 -07:00
def get_tx_history ( self , account = None ) :
2013-03-23 23:34:28 -07:00
with self . transaction_lock :
2013-02-22 10:22:22 -08:00
history = self . transactions . items ( )
2013-04-27 08:48:27 -07:00
history . sort ( key = lambda x : self . verifier . get_txpos ( x [ 0 ] ) )
2013-03-23 23:34:28 -07:00
result = [ ]
2012-11-16 05:39:31 -08:00
2013-03-23 23:34:28 -07:00
balance = 0
for tx_hash , tx in history :
2013-04-05 07:00:34 -07:00
is_relevant , is_mine , v , fee = self . get_tx_value ( tx , account )
2013-03-23 23:34:28 -07:00
if v is not None : balance + = v
2013-04-05 07:00:34 -07:00
c , u = self . get_account_balance ( account )
2013-03-23 23:34:28 -07:00
if balance != c + u :
result . append ( ( ' ' , 1000 , 0 , c + u - balance , None , c + u - balance , None ) )
balance = c + u - balance
for tx_hash , tx in history :
2013-04-05 07:00:34 -07:00
is_relevant , is_mine , value , fee = self . get_tx_value ( tx , account )
if not is_relevant :
continue
2013-03-23 23:34:28 -07:00
if value is not None :
balance + = value
2013-04-05 07:00:34 -07:00
conf , timestamp = self . verifier . get_confirmations ( tx_hash ) if self . verifier else ( None , None )
2013-03-23 23:34:28 -07:00
result . append ( ( tx_hash , conf , is_mine , value , fee , balance , timestamp ) )
2012-11-16 05:39:31 -08:00
return result
2012-01-19 08:11:36 -08:00
2012-11-05 02:08:16 -08:00
def get_label ( self , tx_hash ) :
label = self . labels . get ( tx_hash )
is_default = ( label == ' ' ) or ( label is None )
if is_default : label = self . get_default_label ( tx_hash )
return label , is_default
2013-02-23 02:50:09 -08:00
2012-11-03 01:17:40 -07:00
def get_default_label ( self , tx_hash ) :
tx = self . transactions . get ( tx_hash )
2012-11-18 02:34:52 -08:00
default_label = ' '
2012-11-03 01:17:40 -07:00
if tx :
2013-04-05 07:00:34 -07:00
is_relevant , is_mine , _ , _ = self . get_tx_value ( tx )
2012-11-16 05:39:31 -08:00
if is_mine :
2013-02-22 10:22:22 -08:00
for o in tx . outputs :
o_addr , _ = o
2012-06-10 02:37:11 -07:00
if not self . is_mine ( o_addr ) :
2012-08-12 13:52:28 -07:00
try :
default_label = self . labels [ o_addr ]
except KeyError :
default_label = o_addr
2013-03-14 05:08:50 -07:00
break
else :
default_label = ' (internal) '
2012-01-19 08:11:36 -08:00
else :
2013-02-22 10:22:22 -08:00
for o in tx . outputs :
o_addr , _ = o
2012-01-19 08:11:36 -08:00
if self . is_mine ( o_addr ) and not self . is_change ( o_addr ) :
2012-05-19 02:01:45 -07:00
break
else :
2013-02-22 10:22:22 -08:00
for o in tx . outputs :
o_addr , _ = o
2012-05-19 02:01:45 -07:00
if self . is_mine ( o_addr ) :
break
else :
o_addr = None
if o_addr :
dest_label = self . labels . get ( o_addr )
2012-08-12 13:52:28 -07:00
try :
default_label = self . labels [ o_addr ]
except KeyError :
default_label = o_addr
2012-05-19 02:01:45 -07:00
2012-11-03 01:17:40 -07:00
return default_label
2012-01-19 08:11:36 -08:00
2013-04-05 07:00:34 -07:00
def mktx ( self , outputs , password , fee = None , change_addr = None , account = None ) :
"""
create a transaction
account parameter :
None means use all accounts
- 1 means imported keys
0 , 1 , etc are seed accounts
"""
2012-12-05 07:41:39 -08:00
for address , x in outputs :
2013-03-01 05:27:56 -08:00
assert is_valid ( address )
2012-12-05 07:41:39 -08:00
amount = sum ( map ( lambda x : x [ 1 ] , outputs ) )
2013-04-07 11:25:01 -07:00
inputs , total , fee = self . choose_tx_inputs ( amount , fee , account )
2012-01-19 08:11:36 -08:00
if not inputs :
2012-08-23 18:16:27 -07:00
raise ValueError ( " Not enough funds " )
2012-06-16 09:45:17 -07:00
2013-04-05 07:00:34 -07:00
outputs = self . add_tx_change ( inputs , outputs , amount , fee , total , change_addr , account )
2012-01-19 08:11:36 -08:00
2013-02-24 12:31:11 -08:00
tx = Transaction . from_io ( inputs , outputs )
2013-02-25 00:05:45 -08:00
2013-08-17 01:56:23 -07:00
keypairs = { }
for i , txin in enumerate ( tx . inputs ) :
2013-03-02 14:35:43 -08:00
address = txin [ ' address ' ]
2013-04-05 07:00:34 -07:00
if address in self . imported_keys . keys ( ) :
2013-03-02 14:49:32 -08:00
pk_addresses . append ( address )
continue
2013-03-03 01:24:30 -08:00
account , sequence = self . get_address_index ( address )
2013-08-01 11:08:56 -07:00
txin [ ' KeyID ' ] = ( account , ' BIP32 ' , sequence ) # used by the server to find the key
2013-08-16 03:17:29 -07:00
2013-08-15 06:27:03 -07:00
redeemScript = self . accounts [ account ] . redeem_script ( sequence )
2013-08-16 03:17:29 -07:00
if redeemScript :
txin [ ' redeemScript ' ] = redeemScript
assert address == self . accounts [ account ] . get_address ( * sequence )
2013-08-17 01:56:23 -07:00
else :
txin [ ' redeemPubkey ' ] = self . accounts [ account ] . get_pubkey ( * sequence )
2013-08-16 03:17:29 -07:00
2013-08-17 01:56:23 -07:00
private_keys = self . get_private_key ( address , password )
for sec in private_keys :
compressed = is_compressed ( sec )
pkey = regenerate_key ( sec )
pubkey = GetPubKey ( pkey . pubkey , compressed )
keypairs [ pubkey . encode ( ' hex ' ) ] = sec
2013-08-16 03:17:29 -07:00
2013-08-17 01:56:23 -07:00
tx . sign ( keypairs )
2012-12-05 07:41:39 -08:00
for address , x in outputs :
if address not in self . addressbook and not self . is_mine ( address ) :
2012-12-06 01:36:05 -08:00
self . addressbook . append ( address )
2012-12-05 07:41:39 -08:00
2012-01-19 08:11:36 -08:00
return tx
2013-02-27 00:04:22 -08:00
2012-12-05 09:18:47 -08:00
2012-01-19 08:11:36 -08:00
def sendtx ( self , tx ) :
2012-10-13 23:25:09 -07:00
# synchronous
h = self . send_tx ( tx )
self . tx_event . wait ( )
2012-11-22 04:12:17 -08:00
return self . receive_tx ( h )
2012-10-13 23:25:09 -07:00
def send_tx ( self , tx ) :
# asynchronous
2012-03-24 05:15:23 -07:00
self . tx_event . clear ( )
2013-02-25 09:15:14 -08:00
self . interface . send ( [ ( ' blockchain.transaction.broadcast ' , [ str ( tx ) ] ) ] , ' synchronizer ' )
return tx . hash ( )
2012-10-13 23:25:09 -07:00
def receive_tx ( self , tx_hash ) :
2012-03-24 05:15:23 -07:00
out = self . tx_result
2012-01-19 08:11:36 -08:00
if out != tx_hash :
return False , " error: " + out
return True , out
2012-02-06 08:59:31 -08:00
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
2013-04-29 01:30:29 -07:00
# this will throw an exception if unicode cannot be converted
2013-02-23 02:35:46 -08:00
self . seed = pw_encode ( seed , new_password )
2013-02-03 06:08:26 -08:00
self . config . set_key ( ' seed ' , self . seed , True )
2013-04-29 01:30:29 -07:00
self . use_encryption = ( new_password != None )
self . config . set_key ( ' use_encryption ' , self . use_encryption , True )
2012-02-13 05:52:59 -08:00
for k in self . imported_keys . keys ( ) :
a = self . imported_keys [ k ]
2013-02-23 02:35:46 -08:00
b = pw_decode ( a , old_password )
c = pw_encode ( b , new_password )
2012-02-13 05:56:31 -08:00
self . imported_keys [ k ] = c
2013-04-29 01:30:29 -07:00
self . config . set_key ( ' imported_keys ' , self . imported_keys , True )
2012-03-23 08:34:34 -07:00
2013-08-05 04:53:50 -07:00
for k , v in self . master_private_keys . items ( ) :
b = pw_decode ( v , old_password )
c = pw_encode ( b , new_password )
self . master_private_keys [ k ] = c
self . config . set_key ( ' master_private_keys ' , self . master_private_keys , True )
2012-03-23 09:46:48 -07:00
2012-06-07 02:18:11 -07:00
def freeze ( self , addr ) :
2013-03-02 02:40:17 -08:00
if self . is_mine ( addr ) 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 )
2012-10-12 08:40:37 -07:00
self . config . set_key ( ' frozen_addresses ' , self . frozen_addresses , True )
2012-06-07 02:18:11 -07:00
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 ) :
2013-03-02 02:40:17 -08:00
if self . is_mine ( addr ) and addr in self . frozen_addresses :
2012-06-07 02:18:11 -07:00
self . frozen_addresses . remove ( addr )
2012-10-12 08:40:37 -07:00
self . config . set_key ( ' frozen_addresses ' , self . frozen_addresses , True )
2012-06-07 02:18:11 -07:00
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 ) :
2013-03-03 07:49:42 -08:00
if self . is_mine ( addr ) and addr not in self . prioritized_addresses :
2012-06-07 02:25:23 -07:00
self . unfreeze ( addr )
2012-06-07 02:18:11 -07:00
self . prioritized_addresses . append ( addr )
2012-10-12 08:40:37 -07:00
self . config . set_key ( ' prioritized_addresses ' , self . prioritized_addresses , True )
2012-06-07 02:18:11 -07:00
return True
else :
return False
def unprioritize ( self , addr ) :
2013-03-03 07:49:42 -08:00
if self . is_mine ( addr ) and addr in self . prioritized_addresses :
2012-06-07 02:18:11 -07:00
self . prioritized_addresses . remove ( addr )
2012-10-12 08:40:37 -07:00
self . config . set_key ( ' prioritized_addresses ' , self . prioritized_addresses , True )
2012-06-07 02:18:11 -07:00
return True
else :
return False
2012-10-11 11:10:12 -07:00
2013-05-02 01:00:52 -07:00
def set_fee ( self , fee ) :
if self . fee != fee :
self . fee = fee
self . config . set_key ( ' fee_per_kb ' , self . fee , True )
2012-10-11 11:10:12 -07:00
def save ( self ) :
2013-05-02 01:54:48 -07:00
print_error ( " Warning: wallet.save() is deprecated " )
2013-02-22 10:22:22 -08:00
tx = { }
for k , v in self . transactions . items ( ) :
tx [ k ] = str ( v )
2012-10-11 11:10:12 -07:00
s = {
' use_change ' : self . use_change ,
2013-03-24 01:24:45 -07:00
' fee_per_kb ' : self . fee ,
2012-11-04 06:47:19 -08:00
' addr_history ' : self . history ,
2012-10-11 11:10:12 -07:00
' labels ' : self . labels ,
' contacts ' : self . addressbook ,
' num_zeros ' : self . num_zeros ,
' frozen_addresses ' : self . frozen_addresses ,
' prioritized_addresses ' : self . prioritized_addresses ,
' gap_limit ' : self . gap_limit ,
2013-02-22 10:22:22 -08:00
' transactions ' : tx ,
2012-10-11 11:10:12 -07:00
}
for k , v in s . items ( ) :
self . config . set_key ( k , v )
self . config . save ( )
2012-10-20 17:57:31 -07:00
2012-10-26 01:02:09 -07:00
def set_verifier ( self , verifier ) :
self . verifier = verifier
2012-11-15 03:14:29 -08:00
2012-11-16 01:18:35 -08:00
# review transactions that are in the history
2012-11-15 03:14:29 -08:00
for addr , hist in self . history . items ( ) :
if hist == [ ' * ' ] : continue
for tx_hash , tx_height in hist :
if tx_height > 0 :
# add it in case it was previously unconfirmed
self . verifier . add ( tx_hash , tx_height )
2013-02-22 10:22:22 -08:00
2013-03-24 01:52:36 -07:00
# if we are on a pruning server, remove unverified transactions
vr = self . verifier . transactions . keys ( ) + self . verifier . verified_tx . keys ( )
for tx_hash in self . transactions . keys ( ) :
if tx_hash not in vr :
self . transactions . pop ( tx_hash )
2012-11-07 11:25:23 -08:00
def check_new_history ( self , addr , hist ) :
2012-11-23 06:35:54 -08:00
# check that all tx in hist are relevant
if hist != [ ' * ' ] :
for tx_hash , height in hist :
tx = self . transactions . get ( tx_hash )
if not tx : continue
2013-02-22 10:22:22 -08:00
if not tx . has_address ( addr ) :
2012-11-23 06:35:54 -08:00
return False
# check that we are not "orphaning" a transaction
2012-11-23 08:11:32 -08:00
old_hist = self . history . get ( addr , [ ] )
2012-11-23 06:35:54 -08:00
if old_hist == [ ' * ' ] : return True
for tx_hash , height in old_hist :
if tx_hash in map ( lambda x : x [ 0 ] , hist ) : continue
found = False
for _addr , _hist in self . history . items ( ) :
if _addr == addr : continue
if _hist == [ ' * ' ] : continue
_tx_hist = map ( lambda x : x [ 0 ] , _hist )
if tx_hash in _tx_hist :
found = True
break
if not found :
tx = self . transactions . get ( tx_hash )
2013-01-29 04:19:00 -08:00
# tx might not be there
if not tx : continue
2012-11-23 06:35:54 -08:00
# already verified?
2013-03-14 04:22:06 -07:00
if self . verifier . get_height ( tx_hash ) :
2012-11-23 06:35:54 -08:00
continue
# unconfirmed tx
print_error ( " new history is orphaning transaction: " , tx_hash )
# check that all outputs are not mine, request histories
ext_requests = [ ]
2013-03-03 10:29:03 -08:00
for _addr , _v in tx . outputs :
2012-11-24 11:32:50 -08:00
# assert not self.is_mine(_addr)
2012-11-23 06:35:54 -08:00
ext_requests . append ( ( ' blockchain.address.get_history ' , [ _addr ] ) )
ext_h = self . interface . synchronous_get ( ext_requests )
2013-03-24 04:20:13 -07:00
print_error ( " sync: " , ext_requests , ext_h )
2012-11-23 06:35:54 -08:00
height = None
for h in ext_h :
if h == [ ' * ' ] : continue
for item in h :
if item . get ( ' tx_hash ' ) == tx_hash :
height = item . get ( ' height ' )
if height :
print_error ( " found height for " , tx_hash , height )
self . verifier . add ( tx_hash , height )
else :
print_error ( " removing orphaned tx from history " , tx_hash )
self . transactions . pop ( tx_hash )
2012-11-07 11:25:23 -08:00
return True
def check_new_tx ( self , tx_hash , tx ) :
# 1 check that tx is referenced in addr_history.
addresses = [ ]
for addr , hist in self . history . items ( ) :
2012-11-14 06:33:44 -08:00
if hist == [ ' * ' ] : continue
2012-11-07 11:25:23 -08:00
for txh , height in hist :
if txh == tx_hash :
addresses . append ( addr )
if not addresses :
return False
# 2 check that referencing addresses are in the tx
for addr in addresses :
2013-02-22 10:22:22 -08:00
if not tx . has_address ( addr ) :
2012-11-07 11:25:23 -08:00
return False
return True
2012-10-20 17:57:31 -07:00
class WalletSynchronizer ( threading . Thread ) :
def __init__ ( self , wallet , config ) :
threading . Thread . __init__ ( self )
2012-12-05 01:24:30 -08:00
self . daemon = True
2012-10-20 17:57:31 -07:00
self . wallet = wallet
2013-05-15 01:53:49 -07:00
wallet . synchronizer = self
2012-10-20 17:57:31 -07:00
self . interface = self . wallet . interface
self . interface . register_channel ( ' synchronizer ' )
2012-11-20 12:36:06 -08:00
self . wallet . interface . register_callback ( ' connected ' , lambda : self . wallet . set_up_to_date ( False ) )
2012-10-27 10:20:50 -07:00
self . was_updated = True
2012-11-05 14:10:38 -08:00
self . running = False
self . lock = threading . Lock ( )
def stop ( self ) :
with self . lock : self . running = False
2012-11-24 11:31:07 -08:00
self . interface . poke ( ' synchronizer ' )
2012-11-05 14:10:38 -08:00
def is_running ( self ) :
with self . lock : return self . running
2012-10-20 17:57:31 -07:00
2012-11-07 11:25:23 -08:00
2012-10-20 17:57:31 -07:00
def subscribe_to_addresses ( self , addresses ) :
messages = [ ]
for addr in addresses :
messages . append ( ( ' blockchain.address.subscribe ' , [ addr ] ) )
self . interface . send ( messages , ' synchronizer ' )
def run ( self ) :
2012-11-05 14:10:38 -08:00
with self . lock : self . running = True
2012-11-03 01:17:40 -07:00
requested_tx = [ ]
2012-11-04 07:59:50 -08:00
missing_tx = [ ]
2012-11-06 23:45:53 -08:00
requested_histories = { }
2012-11-04 07:59:50 -08:00
# request any missing transactions
for history in self . wallet . history . values ( ) :
2012-11-14 06:33:44 -08:00
if history == [ ' * ' ] : continue
2012-11-04 07:59:50 -08:00
for tx_hash , tx_height in history :
if self . wallet . transactions . get ( tx_hash ) is None and ( tx_hash , tx_height ) not in missing_tx :
missing_tx . append ( ( tx_hash , tx_height ) )
print_error ( " missing tx " , missing_tx )
2012-10-22 08:18:07 -07:00
# wait until we are connected, in case the user is not connected
while not self . interface . is_connected :
time . sleep ( 1 )
2012-10-20 17:57:31 -07:00
# subscriptions
2013-03-02 02:40:17 -08:00
self . subscribe_to_addresses ( self . wallet . addresses ( True ) )
2012-10-20 17:57:31 -07:00
2012-11-05 14:10:38 -08:00
while self . is_running ( ) :
2013-03-12 15:10:43 -07:00
# 1. create new addresses
new_addresses = self . wallet . synchronize ( )
# request missing addresses
if new_addresses :
self . subscribe_to_addresses ( new_addresses )
2012-10-20 17:57:31 -07:00
2013-03-12 15:10:43 -07:00
# request missing transactions
2012-11-04 07:59:50 -08:00
for tx_hash , tx_height in missing_tx :
if ( tx_hash , tx_height ) not in requested_tx :
self . interface . send ( [ ( ' blockchain.transaction.get ' , [ tx_hash , tx_height ] ) ] , ' synchronizer ' )
requested_tx . append ( ( tx_hash , tx_height ) )
missing_tx = [ ]
2013-03-12 15:10:43 -07:00
# detect if situation has changed
if not self . interface . is_up_to_date ( ' synchronizer ' ) :
if self . wallet . is_up_to_date ( ) :
self . wallet . set_up_to_date ( False )
self . was_updated = True
else :
if not self . wallet . is_up_to_date ( ) :
self . wallet . set_up_to_date ( True )
self . was_updated = True
2012-10-27 10:20:50 -07:00
if self . was_updated :
2012-10-22 04:43:58 -07:00
self . interface . trigger_callback ( ' updated ' )
2012-10-27 10:20:50 -07:00
self . was_updated = False
2012-10-22 04:43:58 -07:00
2012-10-20 17:57:31 -07:00
# 2. get a response
2012-11-24 11:31:07 -08:00
r = self . interface . get_response ( ' synchronizer ' )
2012-11-05 14:10:38 -08:00
2012-11-24 11:31:07 -08:00
# poke sends None. (needed during stop)
if not r : continue
2012-10-20 17:57:31 -07:00
# 3. handle response
method = r [ ' method ' ]
params = r [ ' params ' ]
2012-11-14 06:33:44 -08:00
result = r . get ( ' result ' )
error = r . get ( ' error ' )
if error :
print " error " , r
continue
2012-10-20 17:57:31 -07:00
if method == ' blockchain.address.subscribe ' :
addr = params [ 0 ]
2012-11-07 00:37:14 -08:00
if self . wallet . get_status ( self . wallet . get_history ( addr ) ) != result :
2012-12-14 10:32:10 -08:00
if requested_histories . get ( addr ) is None :
self . interface . send ( [ ( ' blockchain.address.get_history ' , [ addr ] ) ] , ' synchronizer ' )
requested_histories [ addr ] = result
2012-11-06 23:45:53 -08:00
2012-10-20 17:57:31 -07:00
elif method == ' blockchain.address.get_history ' :
addr = params [ 0 ]
2012-11-15 03:14:29 -08:00
print_error ( " receiving history " , addr , result )
2012-11-14 06:33:44 -08:00
if result == [ ' * ' ] :
assert requested_histories . pop ( addr ) == ' * '
self . wallet . receive_history_callback ( addr , result )
else :
hist = [ ]
# check that txids are unique
txids = [ ]
for item in result :
tx_hash = item [ ' tx_hash ' ]
if tx_hash not in txids :
txids . append ( tx_hash )
hist . append ( ( tx_hash , item [ ' height ' ] ) )
if len ( hist ) != len ( result ) :
2012-11-15 00:14:24 -08:00
raise BaseException ( " error: server sent history with non-unique txid " , result )
2012-11-14 06:33:44 -08:00
# check that the status corresponds to what was announced
rs = requested_histories . pop ( addr )
if self . wallet . get_status ( hist ) != rs :
raise BaseException ( " error: status mismatch: %s " % addr )
2012-11-07 00:37:14 -08:00
2012-11-14 06:33:44 -08:00
# store received history
self . wallet . receive_history_callback ( addr , hist )
# request transactions that we don't have
for tx_hash , tx_height in hist :
if self . wallet . transactions . get ( tx_hash ) is None :
if ( tx_hash , tx_height ) not in requested_tx and ( tx_hash , tx_height ) not in missing_tx :
missing_tx . append ( ( tx_hash , tx_height ) )
2012-11-03 01:17:40 -07:00
elif method == ' blockchain.transaction.get ' :
tx_hash = params [ 0 ]
tx_height = params [ 1 ]
2013-02-21 05:18:12 -08:00
assert tx_hash == hash_encode ( Hash ( result . decode ( ' hex ' ) ) )
tx = Transaction ( result )
2013-02-22 10:22:22 -08:00
self . wallet . receive_tx_callback ( tx_hash , tx , tx_height )
2012-10-27 10:20:50 -07:00
self . was_updated = True
2012-11-04 07:59:50 -08:00
requested_tx . remove ( ( tx_hash , tx_height ) )
2013-04-01 03:11:24 -07:00
print_error ( " received tx: " , tx_hash , len ( tx . raw ) )
2012-11-03 01:17:40 -07:00
2012-10-20 17:57:31 -07:00
elif method == ' blockchain.transaction.broadcast ' :
self . wallet . tx_result = result
self . wallet . tx_event . set ( )
else :
2012-10-22 02:34:21 -07:00
print_error ( " Error: Unknown message: " + method + " , " + repr ( params ) + " , " + repr ( result ) )
2012-10-20 17:57:31 -07:00
2012-11-04 07:59:50 -08:00
if self . was_updated and not requested_tx :
2012-10-22 04:43:58 -07:00
self . interface . trigger_callback ( ' updated ' )
2013-06-17 06:12:20 -07:00
self . interface . trigger_callback ( " new_transaction " ) # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
2012-10-20 17:57:31 -07:00
2013-06-17 06:12:20 -07:00
self . was_updated = False