# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import sys, base64, os, re, hashlib, copy, operator, ast, threading, random
+import sys, base64, os, re, hashlib, copy, operator, ast, threading, random, getpass
import aes, ecdsa
from ecdsa.util import string_to_number, number_to_string
bytes = b58decode(addr, 25)
return bytes[1:21]
+def encode_point(pubkey, compressed=False):
+ order = generator_secp256k1.order()
+ p = pubkey.pubkey.point
+ x_str = ecdsa.util.number_to_string(p.x(), order)
+ y_str = ecdsa.util.number_to_string(p.y(), order)
+ if compressed:
+ return chr(2 + (p.y() & 1)) + x_str
+ else:
+ return chr(4) + pubkey.to_string() #x_str + y_str
+
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
__b58base = len(__b58chars)
########### end pywallet functions #######################
+# get password routine
+def prompt_password(prompt, confirm=True):
+ if sys.stdin.isatty():
+ password = getpass.getpass(prompt)
+
+ if password and confirm:
+ password2 = getpass.getpass("Confirm: ")
+
+ if password != password2:
+ sys.stderr.write("Error: Passwords do not match.\n")
+ sys.stderr.flush()
+ sys.exit(1)
+
+ else:
+ password = raw_input(prompt)
+
+ if not password:
+ password = None
+
+ return password
+
# 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)
class Wallet:
- def __init__(self, gui_callback = lambda: None):
+ def __init__(self):
self.electrum_version = ELECTRUM_VERSION
self.seed_version = SEED_VERSION
- self.gui_callback = gui_callback
+ self.update_callbacks = []
self.gap_limit = 5 # configuration
+ self.use_change = True
self.fee = 100000
self.num_zeros = 0
self.master_public_key = ''
self.authorities = {} # trusted addresses
self.frozen_addresses = []
self.prioritized_addresses = []
+ self.expert_mode = False
self.receipts = {} # signed URIs
self.receipt = None # next receipt
self.addressbook = [] # outgoing addresses, for payments
+ self.debug_server = False # write server communication debug info to stdout
# not saved
self.tx_history = {}
self.pick_random_server()
+ def register_callback(self, update_callback):
+ with self.lock:
+ self.update_callbacks.append(update_callback)
+ def trigger_callbacks(self):
+ with self.lock:
+ callbacks = self.update_callbacks[:]
+ [update() for update in callbacks]
def pick_random_server(self):
self.server = random.choice( DEFAULT_SERVERS ) # random choice when the wallet is created
continue
else:
raise BaseException("error: cannot sign message")
-
-
+
+
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
sig = base64.b64decode(signature)
if len(sig) != 65: raise BaseException("Wrong encoding")
r,s = util.sigdecode_string(sig[1:], order)
- recid = ord(sig[0]) - 27
+ nV = ord(sig[0])
+ if nV < 27 or nV >= 35:
+ raise BaseException("Bad encoding")
+ if nV >= 31:
+ compressed = True
+ nV -= 4
+ else:
+ compressed = False
+
+ recid = nV - 27
# 1.1
x = r + (recid/2) * order
# 1.3
# check that Q is the public key
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
# check that we get the original signing address
- addr = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() )
- # print addr
+ addr = public_key_to_bc_address( encode_point(public_key, compressed) )
if address != addr:
- print "bad signature"
raise BaseException("Bad signature")
return address
+ def change_gap_limit(self, value):
+ if value >= self.gap_limit:
+ self.gap_limit = value
+ self.save()
+ self.interface.poke()
+ return True
+
+ elif value >= self.min_acceptable_gap():
+ k = self.num_unused_trailing_addresses()
+ n = len(self.addresses) - k + value
+ self.addresses = self.addresses[0:n]
+ self.gap_limit = value
+ self.save()
+ return True
+ else:
+ return False
+
+ def num_unused_trailing_addresses(self):
+ k = 0
+ for a in self.addresses[::-1]:
+ 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
+ k = self.num_unused_trailing_addresses()
+ for a in self.addresses[0:-k]:
+ if self.history.get(a):
+ n = 0
+ else:
+ n += 1
+ if n > nmax: nmax = n
+ return nmax + 1
+
def synchronize(self):
if not self.master_public_key:
s = {
'seed_version':self.seed_version,
'use_encryption':self.use_encryption,
+ 'use_change':self.use_change,
'master_public_key': self.master_public_key.encode('hex'),
'fee':self.fee,
'server':self.server,
'num_zeros':self.num_zeros,
'frozen_addresses':self.frozen_addresses,
'prioritized_addresses':self.prioritized_addresses,
+ 'expert_mode':self.expert_mode,
+ 'gap_limit':self.gap_limit,
+ 'debug_server':self.debug_server,
}
f = open(self.path,"w")
f.write( repr(s) )
f.close()
+ import stat
+ os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
def read(self):
import interface
f.close()
except:
return
- data = interface.old_to_new(data)
try:
d = ast.literal_eval( data )
+ interface.old_to_new(d)
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.use_change = bool(d.get('use_change',True))
self.fee = int( d.get('fee') )
self.seed = d.get('seed')
self.server = d.get('server')
self.num_zeros = d.get('num_zeros',0)
self.frozen_addresses = d.get('frozen_addresses',[])
self.prioritized_addresses = d.get('prioritized_addresses',[])
+ self.expert_mode = d.get('expert_mode',False)
+ self.gap_limit = d.get('gap_limit',5)
+ self.debug_server = d.get('debug_server',False)
except:
raise BaseException("cannot read wallet file")
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)
+ #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
inputs = []
return inputs, total, fee
default_label = ''
if tx['value']<0:
for o_addr in tx['outputs']:
- if not self.is_change(o_addr):
+ if not self.is_mine(o_addr):
dest_label = self.labels.get(o_addr)
if dest_label:
default_label = 'to: ' + dest_label
inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
if not inputs:
raise BaseException("Not enough funds")
+
+ if not self.use_change and not change_addr:
+ change_addr = inputs[0][0]
+ print "Sending change to", change_addr
+
outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
s_inputs = self.sign_inputs( inputs, outputs, password )
else:
print k,v
+ if label and self.labels.get(address) != label:
+ if question('Give label "%s" to address %s ?'%(label,address)):
+ if address not in self.addressbook and address not in self.all_addresses():
+ self.addressbook.append(address)
+ self.labels[address] = label
+
if signature:
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
signing_address = self.get_alias(identity, True, show_message, question)