commit 7267579fe0792fc7ff65724d1756520d943bc66e
parent bb859c244b219479be2ea0980ff552bd9df362e4
Author: ThomasV <thomasv@gitorious>
Date: Thu, 27 Feb 2014 10:41:22 +0100
Merge branch '2.0'
Diffstat:
14 files changed, 734 insertions(+), 315 deletions(-)
diff --git a/electrum b/electrum
@@ -89,6 +89,7 @@ def arg_parser():
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
+ parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32")
return parser
@@ -105,15 +106,34 @@ def print_help_cb(self, opt, value, parser):
def run_command(cmd, password=None, args=[]):
+ import xmlrpclib, socket
cmd_runner = Commands(wallet, network)
- func = getattr(cmd_runner, cmd)
+ func = getattr(cmd_runner, cmd.name)
cmd_runner.password = password
+
+ if cmd.requires_network and not options.offline:
+ cmd_runner.network = xmlrpclib.ServerProxy('http://localhost:8000')
+ if wallet:
+ wallet.start_threads(cmd_runner.network)
+ wallet.update()
+ else:
+ cmd_runner.network = None
+
try:
result = func(*args[1:])
+ except socket.error:
+ print "Daemon not running"
+ sys.exit(1)
except Exception:
traceback.print_exc(file=sys.stdout)
sys.exit(1)
+
+ if cmd.requires_network and not options.offline:
+ if wallet:
+ wallet.stop_threads()
+
+
if type(result) == str:
util.print_msg(result)
elif result is not None:
@@ -191,15 +211,6 @@ if __name__ == '__main__':
# instanciate wallet for command-line
storage = WalletStorage(config)
- if cmd.requires_wallet:
- wallet = Wallet(storage)
- else:
- wallet = None
-
- if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
- print_msg("Error: Wallet file not found.")
- print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
- sys.exit(0)
if cmd.name in ['create', 'restore']:
if storage.file_exists:
@@ -215,35 +226,26 @@ if __name__ == '__main__':
if not config.get('server'):
config.set_key('server', pick_random_server())
- fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
- gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
-
- if fee:
- wallet.set_fee(float(fee)*100000000)
- if gap:
- wallet.change_gap_limit(int(gap))
+ #fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
+ #gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
+ #if fee:
+ # wallet.set_fee(float(fee)*100000000)
+ #if gap:
+ # wallet.change_gap_limit(int(gap))
if cmd.name == 'restore':
import getpass
seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
- try:
- seed.decode('hex')
- except Exception:
- print_error("Warning: Not hex, trying decode.")
- seed = mnemonic_decode(seed.split(' '))
- if not seed:
- sys.exit("Error: No seed")
-
- wallet.init_seed(str(seed))
+ wallet = Wallet.from_seed(str(seed),storage)
+ if not wallet:
+ sys.exit("Error: Invalid seed")
wallet.save_seed(password)
if not options.offline:
network = Network(config)
network.start()
wallet.start_threads(network)
-
print_msg("Recovering wallet...")
wallet.restore(lambda x: x)
-
if wallet.is_found():
print_msg("Recovery successful")
else:
@@ -253,6 +255,7 @@ if __name__ == '__main__':
print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
else:
+ wallet = Wallet(storage)
wallet.init_seed(None)
wallet.save_seed(password)
wallet.synchronize()
@@ -264,6 +267,19 @@ if __name__ == '__main__':
# terminate
sys.exit(0)
+
+ if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
+ print_msg("Error: Wallet file not found.")
+ print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
+ sys.exit(0)
+
+
+ if cmd.requires_wallet:
+ wallet = Wallet(storage)
+ else:
+ wallet = None
+
+
# important warning
if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
print_msg("WARNING: ALL your private keys are secret.")
@@ -345,19 +361,37 @@ if __name__ == '__main__':
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
args = args[0:cmd.min_args] + [message]
- # open session
- if cmd.requires_network and not options.offline:
- network = Network(config)
- if not network.start(wait=True):
- print_msg("Not connected, aborting.")
- sys.exit(1)
- print_error("Connected to " + network.interface.connection_msg)
- if wallet:
- wallet.start_threads(network)
- wallet.update()
- else:
- network = None
+ if cmd.name == 'daemon' and args[1] == 'start':
+ pid = os.fork()
+ if (pid == 0): # The first child.
+ os.chdir("/")
+ os.setsid()
+ os.umask(0)
+ pid2 = os.fork()
+ if (pid2 == 0): # Second child
+ from SimpleXMLRPCServer import SimpleXMLRPCServer
+ # start the daemon
+ network = Network(config)
+ if not network.start(wait=True):
+ print_msg("Not connected, aborting.")
+ sys.exit(1)
+ print_msg("Connected to " + network.interface.connection_msg)
+ server = SimpleXMLRPCServer(('localhost',8000), allow_none=True, logRequests=False)
+ server.register_function(network.synchronous_get, 'synchronous_get')
+ server.register_function(network.get_servers, 'get_servers')
+ server.register_function(network.main_server, 'main_server')
+ server.register_function(network.send, 'send')
+ server.register_function(network.subscribe, 'subscribe')
+ server.register_function(network.is_connected, 'is_connected')
+ server.register_function(network.is_up_to_date, 'is_up_to_date')
+ server.register_function(lambda: setattr(server,'running', False), 'stop')
+ server.running = True
+ while server.running:
+ server.handle_request()
+ print_msg("Daemon stopped")
+
+ sys.exit(0)
# run the command
if cmd.name == 'deseed':
@@ -394,11 +428,8 @@ if __name__ == '__main__':
wallet.update_password(password, new_password)
else:
- run_command(cmd.name, password, args)
+ run_command(cmd, password, args)
- if network:
- if wallet:
- wallet.stop_threads()
- network.stop()
- time.sleep(0.1)
- sys.exit(0)
+
+ time.sleep(0.1)
+ sys.exit(0)
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
@@ -3,7 +3,7 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
from electrum.i18n import _
-from electrum import Wallet, mnemonic
+from electrum import Wallet
from seed_dialog import SeedDialog
from network_dialog import NetworkDialog
@@ -259,13 +259,13 @@ class InstallWizard(QDialog):
if not action:
return
- wallet = Wallet(self.storage)
- gap = self.config.get('gap_limit', 5)
- if gap != 5:
- wallet.gap_limit = gap
- wallet.storage.put('gap_limit', gap, True)
+ #gap = self.config.get('gap_limit', 5)
+ #if gap != 5:
+ # wallet.gap_limit = gap
+ # wallet.storage.put('gap_limit', gap, True)
if action == 'create':
+ wallet = Wallet(self.storage)
wallet.init_seed(None)
if not self.show_seed(wallet):
return
@@ -277,23 +277,14 @@ class InstallWizard(QDialog):
wallet.synchronize() # generate first addresses offline
self.waiting_dialog(create)
-
elif action == 'restore':
seed = self.seed_dialog()
if not seed:
return
- try:
- wallet.init_seed(seed)
- except Exception:
- import traceback
- traceback.print_exc(file=sys.stdout)
- QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
- return
-
+ wallet = Wallet.from_seed(seed, self.storage)
ok, old_password, password = self.password_dialog(wallet)
wallet.save_seed(password)
-
elif action == 'watching':
mpk = self.mpk_dialog()
if not mpk:
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -426,6 +426,9 @@ class ElectrumWindow(QMainWindow):
raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
raw_transaction_text.triggered.connect(self.do_process_from_text)
+ raw_transaction_text = raw_transaction_menu.addAction(_("&From the blockchain"))
+ raw_transaction_text.triggered.connect(self.do_process_from_txid)
+
help_menu = menubar.addMenu(_("&Help"))
show_about = help_menu.addAction(_("&About"))
@@ -1887,6 +1890,18 @@ class ElectrumWindow(QMainWindow):
if tx:
self.show_transaction(tx)
+ def do_process_from_txid(self):
+ from electrum import transaction
+ txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
+ if ok and txid:
+ r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
+ if r:
+ tx = transaction.Transaction(r)
+ if tx:
+ self.show_transaction(tx)
+ else:
+ self.show_message("unknown transaction")
+
def do_process_from_csvReader(self, csvReader):
outputs = []
try:
diff --git a/lib/__init__.py b/lib/__init__.py
@@ -1,7 +1,7 @@
from version import ELECTRUM_VERSION
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
from wallet import WalletSynchronizer, WalletStorage
-from wallet_factory import WalletFactory as Wallet
+from wallet import Wallet
from verifier import TxVerifier
from network import Network, DEFAULT_SERVERS, DEFAULT_PORTS, pick_random_server
from interface import Interface
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
@@ -53,14 +53,25 @@ def op_push(i):
+def sha256(x):
+ return hashlib.sha256(x).digest()
+
def Hash(x):
if type(x) is unicode: x=x.encode('utf-8')
- return hashlib.sha256(hashlib.sha256(x).digest()).digest()
+ return sha256(sha256(x))
+
hash_encode = lambda x: x[::-1].encode('hex')
hash_decode = lambda x: x.decode('hex')[::-1]
-
hmac_sha_512 = lambda x,y: hmac.new(x, y, hashlib.sha512).digest()
-mnemonic_hash = lambda x: hmac_sha_512("Bitcoin mnemonic", x).encode('hex')
+
+def mnemonic_to_seed(mnemonic, passphrase):
+ from pbkdf2 import PBKDF2
+ import hmac
+ PBKDF2_ROUNDS = 2048
+ return PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
+
+from version import SEED_PREFIX
+is_seed = lambda x: hmac_sha_512("Seed version", x).encode('hex')[0:2].startswith(SEED_PREFIX)
# pywallet openssl private key implementation
@@ -115,11 +126,11 @@ def i2o_ECPublicKey(pubkey, compressed=False):
def hash_160(public_key):
try:
md = hashlib.new('ripemd160')
- md.update(hashlib.sha256(public_key).digest())
+ md.update(sha256(public_key))
return md.digest()
except Exception:
import ripemd
- md = ripemd.new(hashlib.sha256(public_key).digest())
+ md = ripemd.new(sha256(public_key))
return md.digest()
@@ -137,15 +148,6 @@ def bc_address_to_hash_160(addr):
bytes = b58decode(addr, 25)
return ord(bytes[0]), 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)
@@ -233,8 +235,7 @@ def regenerate_key(sec):
if not b:
return False
b = b[0:32]
- secret = int('0x' + b.encode('hex'), 16)
- return EC_KEY(secret)
+ return EC_KEY(b)
def GetPubKey(pubkey, compressed=False):
return i2o_ECPublicKey(pubkey, compressed)
@@ -282,13 +283,14 @@ try:
except Exception:
print "cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa"
exit()
+
from ecdsa.curves import SECP256k1
+from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string
def msg_magic(message):
varint = var_int(len(message))
encoded_varint = "".join([chr(int(varint[i:i+2], 16)) for i in xrange(0, len(varint), 2)])
-
return "\x18Bitcoin Signed Message:\n" + encoded_varint + message
@@ -301,9 +303,66 @@ def verify_message(address, signature, message):
return False
+def chunks(l, n):
+ return [l[i:i+n] for i in xrange(0, len(l), n)]
+
+
+def ECC_YfromX(x,curved=curve_secp256k1, odd=True):
+ _p = curved.p()
+ _a = curved.a()
+ _b = curved.b()
+ for offset in range(128):
+ Mx = x + offset
+ My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
+ My = pow(My2, (_p+1)/4, _p )
+
+ if curved.contains_point(Mx,My):
+ if odd == bool(My&1):
+ return [My,offset]
+ return [_p-My,offset]
+ raise Exception('ECC_YfromX: No Y found')
+
+def private_header(msg,v):
+ assert v<1, "Can't write version %d private header"%v
+ r = ''
+ if v==0:
+ r += ('%08x'%len(msg)).decode('hex')
+ r += sha256(msg)[:2]
+ return ('%02x'%v).decode('hex') + ('%04x'%len(r)).decode('hex') + r
+
+def public_header(pubkey,v):
+ assert v<1, "Can't write version %d public header"%v
+ r = ''
+ if v==0:
+ r = sha256(pubkey)[:2]
+ return '\x6a\x6a' + ('%02x'%v).decode('hex') + ('%04x'%len(r)).decode('hex') + r
+
+
+def negative_point(P):
+ return Point( P.curve(), P.x(), -P.y(), P.order() )
+
+
+def point_to_ser(P, comp=True ):
+ if comp:
+ return ( ('%02x'%(2+(P.y()&1)))+('%064x'%P.x()) ).decode('hex')
+ return ( '04'+('%064x'%P.x())+('%064x'%P.y()) ).decode('hex')
+
+
+def ser_to_point(Aser):
+ curve = curve_secp256k1
+ generator = generator_secp256k1
+ _r = generator.order()
+ assert Aser[0] in ['\x02','\x03','\x04']
+ if Aser[0] == '\x04':
+ return Point( curve, str_to_long(Aser[1:33]), str_to_long(Aser[33:]), _r )
+ Mx = string_to_number(Aser[1:])
+ return Point( curve, Mx, ECC_YfromX(Mx, curve, Aser[0]=='\x03')[0], _r )
+
+
class EC_KEY(object):
- def __init__( self, secret ):
+ def __init__( self, k ):
+ secret = string_to_number(k)
self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
self.secret = secret
@@ -323,10 +382,11 @@ class EC_KEY(object):
else:
raise Exception("error: cannot sign message")
+
@classmethod
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
+ from ecdsa import numbertheory, util
import msqr
curve = curve_secp256k1
G = generator_secp256k1
@@ -352,7 +412,7 @@ class EC_KEY(object):
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
- R = ellipticcurve.Point(curve, x, y, order)
+ R = Point(curve, x, y, order)
# 1.5 compute e from message:
h = Hash( msg_magic(message) )
e = string_to_number(h)
@@ -364,11 +424,94 @@ class EC_KEY(object):
# 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( encode_point(public_key, compressed) )
+ addr = public_key_to_bc_address( point_to_ser(public_key.pubkey.point, compressed) )
if address != addr:
raise Exception("Bad signature")
+ # ecdsa encryption/decryption methods
+ # credits: jackjack, https://github.com/jackjack-jj/jeeq
+
+ @classmethod
+ def encrypt_message(self, message, pubkey):
+ generator = generator_secp256k1
+ curved = curve_secp256k1
+ r = ''
+ msg = private_header(message,0) + message
+ msg = msg + ('\x00'*( 32-(len(msg)%32) ))
+ msgs = chunks(msg,32)
+
+ _r = generator.order()
+ str_to_long = string_to_number
+
+ P = generator
+ if len(pubkey)==33: #compressed
+ pk = Point( curve_secp256k1, str_to_long(pubkey[1:33]), ECC_YfromX(str_to_long(pubkey[1:33]), curve_secp256k1, pubkey[0]=='\x03')[0], _r )
+ else:
+ pk = Point( curve_secp256k1, str_to_long(pubkey[1:33]), str_to_long(pubkey[33:65]), _r )
+
+ for i in range(len(msgs)):
+ n = ecdsa.util.randrange( pow(2,256) )
+ Mx = str_to_long(msgs[i])
+ My, xoffset = ECC_YfromX(Mx, curved)
+ M = Point( curved, Mx+xoffset, My, _r )
+ T = P*n
+ U = pk*n + M
+ toadd = point_to_ser(T) + point_to_ser(U)
+ toadd = chr(ord(toadd[0])-2 + 2*xoffset) + toadd[1:]
+ r += toadd
+
+ return base64.b64encode(public_header(pubkey,0) + r)
+
+
+ def decrypt_message(self, enc):
+ G = generator_secp256k1
+ curved = curve_secp256k1
+ pvk = self.secret
+ pubkeys = [point_to_ser(G*pvk,True), point_to_ser(G*pvk,False)]
+ enc = base64.b64decode(enc)
+ str_to_long = string_to_number
+
+ assert enc[:2]=='\x6a\x6a'
+
+ phv = str_to_long(enc[2])
+ assert phv==0, "Can't read version %d public header"%phv
+ hs = str_to_long(enc[3:5])
+ public_header=enc[5:5+hs]
+ checksum_pubkey=public_header[:2]
+ address=filter(lambda x:sha256(x)[:2]==checksum_pubkey, pubkeys)
+ assert len(address)>0, 'Bad private key'
+ address=address[0]
+ enc=enc[5+hs:]
+ r = ''
+ for Tser,User in map(lambda x:[x[:33],x[33:]], chunks(enc,66)):
+ ots = ord(Tser[0])
+ xoffset = ots>>1
+ Tser = chr(2+(ots&1))+Tser[1:]
+ T = ser_to_point(Tser)
+ U = ser_to_point(User)
+ V = T*pvk
+ Mcalc = U + negative_point(V)
+ r += ('%064x'%(Mcalc.x()-xoffset)).decode('hex')
+
+ pvhv = str_to_long(r[0])
+ assert pvhv==0, "Can't read version %d private header"%pvhv
+ phs = str_to_long(r[1:3])
+ private_header = r[3:3+phs]
+ size = str_to_long(private_header[:4])
+ checksum = private_header[4:6]
+ r = r[3+phs:]
+
+ msg = r[:size]
+ hashmsg = sha256(msg)[:2]
+ checksumok = hashmsg==checksum
+
+ return [msg, checksumok, address]
+
+
+
+
+
###################################### BIP32 ##############################
random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
@@ -408,7 +551,7 @@ def CKD(k, c, n):
import hmac
from ecdsa.util import string_to_number, number_to_string
order = generator_secp256k1.order()
- keypair = EC_KEY(string_to_number(k))
+ keypair = EC_KEY(k)
K = GetPubKey(keypair.pubkey,True)
if n & BIP32_PRIME: # We want to make a "secret" address that can't be determined from K
@@ -531,8 +674,35 @@ def test_bip32(seed, sequence):
+def test_crypto():
+
+ G = generator_secp256k1
+ _r = G.order()
+ pvk = ecdsa.util.randrange( pow(2,256) ) %_r
+
+ Pub = pvk*G
+ pubkey_c = point_to_ser(Pub,True)
+ pubkey_u = point_to_ser(Pub,False)
+ addr_c = public_key_to_bc_address(pubkey_c)
+ addr_u = public_key_to_bc_address(pubkey_u)
+
+ print "Private key ", '%064x'%pvk
+ print "Compressed public key ", pubkey_c.encode('hex')
+ print "Uncompressed public key", pubkey_u.encode('hex')
+
+ message = "Chancellor on brink of second bailout for banks"
+ enc = EC_KEY.encrypt_message(message,pubkey_c)
+ eck = EC_KEY(number_to_string(pvk,_r))
+ dec = eck.decrypt_message(enc)
+ print "decrypted", dec
+
+ signature = eck.sign_message(message, True, addr_c)
+ print signature
+ EC_KEY.verify_message(addr_c, signature, message)
+
if __name__ == '__main__':
- test_bip32("000102030405060708090a0b0c0d0e0f", "0'/1/2'/2/1000000000")
- test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","0/2147483647'/1/2147483646'/2")
+ test_crypto()
+ #test_bip32("000102030405060708090a0b0c0d0e0f", "0'/1/2'/2/1000000000")
+ #test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","0/2147483647'/1/2147483646'/2")
diff --git a/lib/commands.py b/lib/commands.py
@@ -67,9 +67,9 @@ register_command('dumpprivkeys', 0, 0, False, True, True, 'dump all pr
register_command('freeze', 1, 1, False, True, True, 'Freeze the funds at one of your wallet\'s addresses', 'freeze <address>')
register_command('getbalance', 0, 1, True, True, False, 'Return the balance of your wallet, or of one account in your wallet', 'getbalance [<account>]')
register_command('getservers', 0, 0, True, False, False, 'Return the list of available servers')
-register_command('getversion', 0, 0, False, False, False, 'Return the version of your client', 'getversion')
-register_command('getaddressbalance', 1, 1, True, True, False, 'Return the balance of an address', 'getaddressbalance <address>')
-register_command('getaddresshistory', 1, 1, True, True, False, 'Return the transaction history of a wallet address', 'getaddresshistory <address>')
+register_command('getversion', 0, 0, False, False, False, 'Return the version of your client', 'getversion')
+register_command('getaddressbalance', 1, 1, True, False, False, 'Return the balance of an address', 'getaddressbalance <address>')
+register_command('getaddresshistory', 1, 1, True, False, False, 'Return the transaction history of a wallet address', 'getaddresshistory <address>')
register_command('getconfig', 1, 1, False, False, False, 'Return a configuration variable', 'getconfig <name>')
register_command('getpubkeys', 1, 1, False, True, False, 'Return the public keys for a wallet address', 'getpubkeys <bitcoin address>')
register_command('getrawtransaction', 1, 1, True, False, False, 'Retrieve a transaction', 'getrawtransaction <txhash>')
@@ -79,7 +79,8 @@ register_command('help', 0, 1, False, False, False, 'Prints this
register_command('history', 0, 0, True, True, False, 'Returns the transaction history of your wallet')
register_command('importprivkey', 1, 1, False, True, True, 'Import a private key', 'importprivkey <privatekey>')
register_command('listaddresses', 2, 2, False, True, False, 'Returns your list of addresses.', '', listaddr_options)
-register_command('listunspent', 0, 0, True, True, False, 'Returns the list of unspent inputs in your wallet.')
+register_command('listunspent', 0, 0, True, False, False, 'Returns the list of unspent inputs in your wallet.')
+register_command('getaddressunspent', 1, 1, True, False, False, 'Returns the list of unspent inputs in your wallet.')
register_command('mktx', 5, 5, False, True, True, 'Create a signed transaction', 'mktx <recipient> <amount> [label]', payto_options)
register_command('mksendmanytx', 4, 4, False, True, True, 'Create a signed transaction', mksendmany_syntax, payto_options)
register_command('payto', 5, 5, True, True, True, 'Create and broadcast a transaction.', payto_syntax, payto_options)
@@ -95,6 +96,12 @@ register_command('unfreeze', 1, 1, False, True, False, 'Unfreeze th
register_command('validateaddress', 1, 1, False, False, False, 'Check that the address is valid', 'validateaddress <address>')
register_command('verifymessage', 3,-1, False, False, False, 'Verifies a signature', verifymessage_syntax)
+register_command('encrypt', 2,-1, False, False, False, 'encrypt a message with pubkey','encrypt <pubkey> <message>')
+register_command('decrypt', 2,-1, False, False, False, 'decrypt a message with privkey','decrypt <privkey> <message>')
+register_command('daemon', 1, 1, True, False, False, 'start/stop daemon')
+register_command('getproof', 1, 1, True, False, False, 'get merkle proof', 'getproof <address>')
+register_command('getunspentaddress', 2, 2, True, False, False, 'get the address of an unspent','getunspentaddress <txid> <pos>')
+
@@ -106,6 +113,7 @@ class Commands:
self._callback = callback
self.password = None
+
def _run(self, method, args, password_getter):
cmd = known_commands[method]
if cmd.requires_password and self.wallet.use_encryption:
@@ -117,11 +125,22 @@ class Commands:
apply(self._callback, ())
return result
+
def getaddresshistory(self, addr):
- assert self.wallet.is_mine(addr)
- h = self.wallet.get_history(addr)
- if h is None: h = self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
- return h
+ return self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
+
+
+ def daemon(self, arg):
+ if arg=='stop':
+ return self.network.stop()
+ elif arg=='status':
+ return {
+ 'server':self.network.main_server(),
+ 'connected':self.network.is_connected()
+ }
+ else:
+ return "unknown command \"%s\""% arg
+
def listunspent(self):
import copy
@@ -129,6 +148,15 @@ class Commands:
for i in l: i["value"] = str(Decimal(i["value"])/100000000)
return l
+
+ def getaddressunspent(self, addr):
+ return self.network.synchronous_get([ ('blockchain.address.listunspent',[addr]) ])[0]
+
+
+ def getunspentaddress(self, txid, num):
+ return self.network.synchronous_get([ ('blockchain.utxo.get_address',[txid, num]) ])[0]
+
+
def createrawtransaction(self, inputs, outputs):
# convert to own format
for i in inputs:
@@ -199,9 +227,14 @@ class Commands:
return out
def getaddressbalance(self, addr):
- c, u = self.wallet.get_addr_balance(addr)
- out = { "confirmed": str(Decimal(c)/100000000) }
- if u: out["unconfirmed"] = str(Decimal(u)/100000000)
+ b = self.network.synchronous_get([ ('blockchain.address.get_balance',[addr]) ])[0]
+ return str(Decimal(b)/100000000)
+
+ def getproof(self, addr):
+ p = self.network.synchronous_get([ ('blockchain.address.get_proof',[addr]) ])[0]
+ out = []
+ for i,s in p:
+ out.append(i)
return out
def getservers(self):
@@ -350,11 +383,27 @@ class Commands:
if cmd.options: print_msg("options:\n" + cmd.options)
return None
+
def getrawtransaction(self, tx_hash):
+ import transaction
if self.wallet:
tx = self.wallet.transactions.get(tx_hash)
if tx:
return tx
- return self.network.retrieve_transaction(tx_hash)
+
+ r = self.network.synchronous_get([ ('blockchain.transaction.get',[tx_hash]) ])[0]
+ if r:
+ return transaction.Transaction(r)
+ else:
+ return "unknown transaction"
+
+ def encrypt(self, pubkey, message):
+ return EC_KEY.encrypt_message(message, pubkey.decode('hex'))
+
+ def decrypt(self, secret, message):
+ ec = regenerate_key(secret)
+ decrypted = ec.decrypt_message(message)
+ return decrypted[0]
+
diff --git a/lib/interface.py b/lib/interface.py
@@ -598,6 +598,22 @@ class Interface(threading.Thread):
self.queue.put(self)
+ def synchronous_get(self, requests, timeout=100000000):
+ queue = Queue.Queue()
+ ids = self.send(requests, lambda i,r: queue.put(r))
+ id2 = ids[:]
+ res = {}
+ while ids:
+ r = queue.get(True, timeout)
+ _id = r.get('id')
+ if _id in ids:
+ ids.remove(_id)
+ res[_id] = r.get('result')
+ out = []
+ for _id in id2:
+ out.append(res[_id])
+ return out
+
if __name__ == "__main__":
diff --git a/lib/network.py b/lib/network.py
@@ -22,6 +22,37 @@ DEFAULT_SERVERS = {
}
+def parse_servers(result):
+ """ parse servers list into dict format"""
+ from version import PROTOCOL_VERSION
+ servers = {}
+ for item in result:
+ host = item[1]
+ out = {}
+ version = None
+ pruning_level = '-'
+ if len(item) > 2:
+ for v in item[2]:
+ if re.match("[stgh]\d*", v):
+ protocol, port = v[0], v[1:]
+ if port == '': port = DEFAULT_PORTS[protocol]
+ out[protocol] = port
+ elif re.match("v(.?)+", v):
+ version = v[1:]
+ elif re.match("p\d*", v):
+ pruning_level = v[1:]
+ if pruning_level == '': pruning_level = '0'
+ try:
+ is_recent = float(version)>=float(PROTOCOL_VERSION)
+ except Exception:
+ is_recent = False
+
+ if out and is_recent:
+ out['pruning'] = pruning_level
+ servers[host] = out
+
+ return servers
+
def filter_protocol(servers, p):
@@ -66,6 +97,8 @@ class Network(threading.Thread):
self.interface = None
self.proxy = self.config.get('proxy')
self.heights = {}
+ self.merkle_roots = {}
+ self.utxo_roots = {}
self.server_lag = 0
dir_path = os.path.join( self.config.path, 'certs')
@@ -82,6 +115,14 @@ class Network(threading.Thread):
return self.interface and self.interface.is_connected
+ def is_up_to_date(self):
+ return self.interface.is_up_to_date()
+
+
+ def main_server(self):
+ return self.interface.server
+
+
def send_subscriptions(self):
for cb, sub in self.subscriptions.items():
self.interface.send(sub, cb)
@@ -327,6 +368,8 @@ class Network(threading.Thread):
if not result: return
height = result.get('block_height')
self.heights[i.server] = height
+ self.merkle_roots[i.server] = result.get('merkle_root')
+ self.utxo_roots[i.server] = result.get('utxo_root')
# notify blockchain about the new height
self.blockchain.queue.put((i,result))
@@ -341,7 +384,7 @@ class Network(threading.Thread):
def on_peers(self, i, r):
if not r: return
- self.irc_servers = self.parse_servers(r.get('result'))
+ self.irc_servers = parse_servers(r.get('result'))
self.trigger_callback('peers')
def on_banner(self, i, r):
@@ -356,59 +399,26 @@ class Network(threading.Thread):
def synchronous_get(self, requests, timeout=100000000):
- queue = Queue.Queue()
- ids = self.interface.send(requests, lambda i,r: queue.put(r))
- id2 = ids[:]
- res = {}
- while ids:
- r = queue.get(True, timeout)
- _id = r.get('id')
- if _id in ids:
- ids.remove(_id)
- res[_id] = r.get('result')
- out = []
- for _id in id2:
- out.append(res[_id])
- return out
+ return self.interface.synchronous_get(requests)
+
+
+
+ #def retrieve_transaction(self, tx_hash, tx_height=0):
+ # import transaction
+ # r = self.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0]
+ # if r:
+ # return transaction.Transaction(r)
+
+
+
- def retrieve_transaction(self, tx_hash, tx_height=0):
- import transaction
- r = self.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0]
- if r:
- return transaction.Transaction(r)
-
-
- def parse_servers(self, result):
- """ parse servers list into dict format"""
- from version import PROTOCOL_VERSION
- servers = {}
- for item in result:
- host = item[1]
- out = {}
- version = None
- pruning_level = '-'
- if len(item) > 2:
- for v in item[2]:
- if re.match("[stgh]\d*", v):
- protocol, port = v[0], v[1:]
- if port == '': port = DEFAULT_PORTS[protocol]
- out[protocol] = port
- elif re.match("v(.?)+", v):
- version = v[1:]
- elif re.match("p\d*", v):
- pruning_level = v[1:]
- if pruning_level == '': pruning_level = '0'
- try:
- is_recent = float(version)>=float(PROTOCOL_VERSION)
- except Exception:
- is_recent = False
-
- if out and is_recent:
- out['pruning'] = pruning_level
- servers[host] = out
-
- return servers
+class NetworkProxy:
+ # interface to the network object.
+ # handle subscriptions and callbacks
+ # the network object can be jsonrpc server
+ def __init__(self, network):
+ self.network = network
diff --git a/lib/version.py b/lib/version.py
@@ -1,5 +1,5 @@
ELECTRUM_VERSION = "1.9.8" # version of the client package
-PROTOCOL_VERSION = '0.6' # protocol version requested
-SEED_VERSION = 4 # bump this every time the seed generation is modified
+PROTOCOL_VERSION = '0.9' # protocol version requested
+NEW_SEED_VERSION = 6 # bip32 wallets
+OLD_SEED_VERSION = 4 # old electrum deterministic generation
SEED_PREFIX = '01' # the hash of the mnemonic seed must begin with this
-
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -73,6 +73,7 @@ class WalletStorage:
def __init__(self, config):
self.lock = threading.Lock()
+ self.config = config
self.data = {}
self.file_exists = False
self.path = self.init_path(config)
@@ -151,7 +152,10 @@ class WalletStorage:
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
-class Wallet:
+
+
+
+class NewWallet:
def __init__(self, storage):
@@ -160,7 +164,7 @@ class Wallet:
self.gap_limit_for_change = 3 # constant
# saved fields
- self.seed_version = storage.get('seed_version', SEED_VERSION)
+ self.seed_version = storage.get('seed_version', NEW_SEED_VERSION)
self.gap_limit = storage.get('gap_limit', 5)
self.use_change = storage.get('use_change',True)
@@ -180,12 +184,6 @@ class Wallet:
self.next_addresses = storage.get('next_addresses',{})
- if self.seed_version not in [4, 6]:
- msg = "This wallet seed is not supported."
- if self.seed_version in [5]:
- msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%self.seed_version
- print msg
- sys.exit(1)
# This attribute is set when wallet.start_threads is called.
self.synchronizer = None
@@ -290,42 +288,27 @@ class Wallet:
# we keep only 13 words, that's approximately 139 bits of entropy
words = mnemonic.mn_encode(s)[0:13]
seed = ' '.join(words)
- if mnemonic_hash(seed).startswith(SEED_PREFIX):
- break # this removes 12 bits of entropy
+ if is_seed(seed):
+ break # this will remove 8 bits of entropy
nonce += 1
return seed
def init_seed(self, seed):
- import mnemonic
+ import mnemonic, unicodedata
if self.seed:
raise Exception("a seed exists")
- if not seed:
- self.seed = random_seed(128)
- self.seed_version = 4
- return
+ self.seed_version = NEW_SEED_VERSION
- # check if seed is hexadecimal
- seed = seed.strip()
- try:
- assert seed
- seed.decode('hex')
- self.seed_version = 4
- self.seed = str(seed)
+ if not seed:
+ self.seed = self.make_seed()
return
- except Exception:
- pass
- words = seed.split()
- self.seed_version = 4
- self.seed = mnemonic.mn_decode(words)
+ self.seed = unicodedata.normalize('NFC', unicode(seed.strip()))
- if not self.seed:
- raise Exception("Invalid seed")
-
def save_seed(self, password):
@@ -338,41 +321,26 @@ class Wallet:
self.create_accounts(password)
- def create_watching_only_wallet(self, params):
- K0, c0 = params
- if not K0:
- return
-
- if not c0:
- self.seed_version = 4
- self.storage.put('seed_version', self.seed_version, True)
- self.create_old_account(K0)
- return
-
- cK0 = ""
+ def create_watching_only_wallet(self, K0, c0):
+ cK0 = "" #FIXME
self.master_public_keys = {
"m/0'/": (c0, K0, cK0),
}
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('seed_version', self.seed_version, True)
- self.create_account('1','Main account')
+ self.create_account('1of1','Main account')
def create_accounts(self, password):
seed = pw_decode(self.seed, password)
-
- if self.seed_version == 4:
- mpk = OldAccount.mpk_from_seed(seed)
- self.create_old_account(mpk)
- else:
- # create default account
- self.create_master_keys('1', password)
- self.create_account('1','Main account')
+ # create default account
+ self.create_master_keys('1of1', password)
+ self.create_account('1of1','Main account')
def create_master_keys(self, account_type, password):
- master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
- if account_type == '1':
+ master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(password))
+ if account_type == '1of1':
k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
self.master_public_keys["m/0'/"] = (c0, K0, cK0)
self.master_private_keys["m/0'/"] = pw_encode(k0, password)
@@ -398,7 +366,7 @@ class Wallet:
self.storage.put('master_private_keys', self.master_private_keys, True)
def has_master_public_keys(self, account_type):
- if account_type == '1':
+ if account_type == '1of1':
return "m/0'/" in self.master_public_keys
elif account_type == '2of2':
return set(["m/1'/", "m/2'/"]) <= set(self.master_public_keys.keys())
@@ -421,17 +389,18 @@ class Wallet:
def deseed_branch(self, k):
# check that parent has no seed
- assert self.seed == ''
+ # assert self.seed == ''
self.master_private_keys.pop(k)
self.storage.put('master_private_keys', self.master_private_keys, True)
+
def is_watching_only(self):
return (self.seed == '') and (self.master_private_keys == {})
def account_id(self, account_type, i):
- if account_type == '1':
+ if account_type == '1of1':
return "m/0'/%d"%i
elif account_type == '2of2':
return "m/1'/%d & m/2'/%d"%(i,i)
@@ -451,7 +420,7 @@ class Wallet:
return i
- def new_account_address(self, account_type = '1'):
+ def new_account_address(self, account_type = '1of1'):
i = self.num_accounts(account_type)
k = self.account_id(account_type,i)
@@ -465,12 +434,12 @@ class Wallet:
return k, addr
- def next_account(self, account_type = '1'):
+ def next_account(self, account_type = '1of1'):
i = self.num_accounts(account_type)
account_id = self.account_id(account_type,i)
- if account_type is '1':
+ if account_type is '1of1':
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({ 'c':c0, 'K':K0, 'cK':cK0 })
@@ -514,7 +483,7 @@ class Wallet:
- def create_account(self, account_type = '1', name = None):
+ def create_account(self, account_type = '1of1', name = None):
k, account = self.next_account(account_type)
if k in self.pending_accounts:
self.pending_accounts.pop(k)
@@ -526,12 +495,6 @@ class Wallet:
self.set_label(k, name)
- def create_old_account(self, mpk):
- self.storage.put('master_public_key', mpk, True)
- self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
- self.save_accounts()
-
-
def save_accounts(self):
d = {}
for k, v in self.accounts.items():
@@ -596,14 +559,13 @@ class Wallet:
return s[0] == 1
def get_master_public_key(self):
- if self.seed_version == 4:
- return self.storage.get("master_public_key")
- else:
- c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
- return repr((c, K))
+ c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
+ return repr((c, K))
def get_master_private_key(self, account, password):
- master_k = pw_decode( self.master_private_keys[account], password)
+ k = self.master_private_keys.get(account)
+ if not k: return
+ master_k = pw_decode( k, password)
master_c, master_K, master_Kc = self.master_public_keys[account]
try:
K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
@@ -675,25 +637,14 @@ class Wallet:
return '&'.join(dd)
-
def get_seed(self, password):
s = pw_decode(self.seed, password)
- if self.seed_version == 4:
- seed = s
- self.accounts[0].check_seed(seed)
- else:
- seed = mnemonic_hash(s)
+ seed = mnemonic_to_seed(s,'').encode('hex')
return seed
-
- def get_mnemonic(self, password):
- import mnemonic
- s = pw_decode(self.seed, password)
- if self.seed_version == 4:
- return ' '.join(mnemonic.mn_encode(s))
- else:
- return s
+ def get_mnemonic(self, password):
+ return pw_decode(self.seed, password)
def get_private_key(self, address, password):
@@ -746,23 +697,6 @@ class Wallet:
for txin in tx.inputs:
keyid = txin.get('KeyID')
if keyid:
-
- if self.seed_version == 4:
- m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
- if not m: continue
- mpk = m.group(1)
- if mpk != self.storage.get('master_public_key'): continue
- for_change = int(m.group(2))
- num = int(m.group(3))
- account = self.accounts[0]
- addr = account.get_address(for_change, num)
- txin['address'] = addr # fixme: side effect
- pk = account.get_private_key(seed, (for_change, num))
- pubkey = public_key_from_private_key(pk)
- keypairs[pubkey] = pk
- continue
-
-
roots = []
for s in keyid.split('&'):
m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
@@ -921,7 +855,7 @@ class Wallet:
def create_pending_accounts(self):
- for account_type in ['1','2of2','2of3']:
+ for account_type in ['1of1','2of2','2of3']:
if not self.has_master_public_keys(account_type):
continue
k, a = self.new_account_address(account_type)
@@ -1050,29 +984,23 @@ class Wallet:
def get_account_name(self, k):
- if k == 0:
- if self.seed_version == 4:
- name = 'Main account'
+ default = "Unnamed account"
+ m = re.match("m/0'/(\d+)", k)
+ if m:
+ num = m.group(1)
+ if num == '0':
+ default = "Main account"
else:
- name = 'Old account'
- else:
- default = "Unnamed account"
- m = re.match("m/0'/(\d+)", k)
- if m:
- num = m.group(1)
- if num == '0':
- default = "Main account"
- else:
- default = "Account %s"%num
+ default = "Account %s"%num
- m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
- if m:
- num = m.group(1)
- default = "2of2 account %s"%num
- name = self.labels.get(k, default)
-
+ m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
+ if m:
+ num = m.group(1)
+ default = "2of2 account %s"%num
+ name = self.labels.get(k, default)
return name
+
def get_account_names(self):
accounts = {}
for k, account in self.accounts.items():
@@ -1081,6 +1009,7 @@ class Wallet:
accounts[-1] = 'Imported keys'
return accounts
+
def get_account_addresses(self, a, include_change=True):
if a is None:
o = self.addresses(True)
@@ -1537,7 +1466,7 @@ class Wallet:
def start_threads(self, network):
from verifier import TxVerifier
self.network = network
- if self.network:
+ if self.network is not None:
self.verifier = TxVerifier(self.network, self.storage)
self.verifier.start()
self.set_verifier(self.verifier)
@@ -1622,12 +1551,12 @@ class WalletSynchronizer(threading.Thread):
if not self.network.is_connected():
self.network.wait_until_connected()
- self.run_interface(self.network.interface)
+ self.run_interface()
- def run_interface(self, interface):
+ def run_interface(self):
- print_error("synchronizer: connected to", interface.server)
+ print_error("synchronizer: connected to", self.network.main_server())
requested_tx = []
missing_tx = []
@@ -1657,12 +1586,12 @@ class WalletSynchronizer(threading.Thread):
# request missing transactions
for tx_hash, tx_height in missing_tx:
if (tx_hash, tx_height) not in requested_tx:
- interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
+ self.network.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
requested_tx.append( (tx_hash, tx_height) )
missing_tx = []
# detect if situation has changed
- if interface.is_up_to_date() and self.queue.empty():
+ if self.network.is_up_to_date() and self.queue.empty():
if not self.wallet.is_up_to_date():
self.wallet.set_up_to_date(True)
self.was_updated = True
@@ -1672,7 +1601,7 @@ class WalletSynchronizer(threading.Thread):
self.was_updated = True
if self.was_updated:
- self.wallet.network.trigger_callback('updated')
+ self.network.trigger_callback('updated')
self.was_updated = False
# 2. get a response
@@ -1681,8 +1610,9 @@ class WalletSynchronizer(threading.Thread):
except Queue.Empty:
continue
- if interface != self.network.interface:
- break
+ # see if it changed
+ #if interface != self.network.interface:
+ # break
if not r:
continue
@@ -1700,7 +1630,7 @@ class WalletSynchronizer(threading.Thread):
addr = params[0]
if self.wallet.get_status(self.wallet.get_history(addr)) != result:
if requested_histories.get(addr) is None:
- interface.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
+ self.network.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
requested_histories[addr] = result
elif method == 'blockchain.address.get_history':
@@ -1750,7 +1680,163 @@ class WalletSynchronizer(threading.Thread):
print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
if self.was_updated and not requested_tx:
- self.wallet.network.trigger_callback('updated')
- self.wallet.network.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
-
+ self.network.trigger_callback('updated')
+ # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
+ self.network.trigger_callback("new_transaction")
self.was_updated = False
+
+
+
+
+class OldWallet(NewWallet):
+
+ def init_seed(self, seed):
+ import mnemonic
+
+ if self.seed:
+ raise Exception("a seed exists")
+
+ if not seed:
+ seed = random_seed(128)
+
+ self.seed_version = OLD_SEED_VERSION
+
+ # see if seed was entered as hex
+ seed = seed.strip()
+ try:
+ assert seed
+ seed.decode('hex')
+ self.seed = str(seed)
+ return
+ except Exception:
+ pass
+
+ words = seed.split()
+ try:
+ mnemonic.mn_decode(words)
+ except Exception:
+ raise
+
+ self.seed = mnemonic.mn_decode(words)
+
+ if not self.seed:
+ raise Exception("Invalid seed")
+
+
+
+ def get_master_public_key(self):
+ return self.storage.get("master_public_key")
+
+ def create_accounts(self, password):
+ seed = pw_decode(self.seed, password)
+ mpk = OldAccount.mpk_from_seed(seed)
+ self.create_account(mpk)
+
+ def create_account(self, mpk):
+ self.storage.put('master_public_key', mpk, True)
+ self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
+ self.save_accounts()
+
+ def create_watching_only_wallet(self, K0):
+ self.seed_version = OLD_SEED_VERSION
+ self.storage.put('seed_version', self.seed_version, True)
+ self.create_account(K0)
+
+ def get_seed(self, password):
+ seed = pw_decode(self.seed, password)
+ self.accounts[0].check_seed(seed)
+ return seed
+
+ def get_mnemonic(self, password):
+ import mnemonic
+ s = pw_decode(self.seed, password)
+ return ' '.join(mnemonic.mn_encode(s))
+
+
+ def add_keypairs_from_KeyID(self, tx, keypairs, password):
+ # first check the provided password
+ seed = self.get_seed(password)
+ for txin in tx.inputs:
+ keyid = txin.get('KeyID')
+ if keyid:
+ m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
+ if not m: continue
+ mpk = m.group(1)
+ if mpk != self.storage.get('master_public_key'): continue
+ for_change = int(m.group(2))
+ num = int(m.group(3))
+ account = self.accounts[0]
+ addr = account.get_address(for_change, num)
+ txin['address'] = addr # fixme: side effect
+ pk = account.get_private_key(seed, (for_change, num))
+ pubkey = public_key_from_private_key(pk)
+ keypairs[pubkey] = pk
+
+
+ def get_account_name(self, k):
+ assert k == 0
+ return 'Main account'
+
+
+
+
+# former WalletFactory
+class Wallet(object):
+
+ def __new__(self, storage):
+ config = storage.config
+ if config.get('bitkey', False):
+ # if user requested support for Bitkey device,
+ # import Bitkey driver
+ from wallet_bitkey import WalletBitkey
+ return WalletBitkey(config)
+
+ if not storage.file_exists:
+ seed_version = NEW_SEED_VERSION if config.get('bip32') is True else OLD_SEED_VERSION
+ else:
+ seed_version = storage.get('seed_version')
+
+ if seed_version == OLD_SEED_VERSION:
+ return OldWallet(storage)
+ elif seed_version == NEW_SEED_VERSION:
+ return NewWallet(storage)
+ else:
+ msg = "This wallet seed is not supported."
+ if seed_version in [5]:
+ msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
+ print msg
+ sys.exit(1)
+
+
+
+
+ @classmethod
+ def from_seed(self, seed, storage):
+ import mnemonic
+ if not seed:
+ return
+
+ words = seed.strip().split()
+ try:
+ mnemonic.mn_decode(words)
+ uses_electrum_words = True
+ except Exception:
+ uses_electrum_words = False
+
+ try:
+ seed.decode('hex')
+ is_hex = True
+ except Exception:
+ is_hex = False
+
+ if is_hex or (uses_electrum_words and len(words) != 13):
+ print "old style wallet", len(words), words
+ w = OldWallet(storage)
+ w.init_seed(seed) #hex
+ else:
+ #assert is_seed(seed)
+ w = Wallet(storage)
+ w.init_seed(seed)
+
+
+ return w
diff --git a/lib/wallet_factory.py b/lib/wallet_factory.py
@@ -1,11 +0,0 @@
-class WalletFactory(object):
- def __new__(cls, config):
- if config.get('bitkey', False):
- # if user requested support for Bitkey device,
- # import Bitkey driver
- from wallet_bitkey import WalletBitkey
- return WalletBitkey(config)
-
- # Load standard wallet
- from wallet import Wallet
- return Wallet(config)
diff --git a/scripts/block_headers b/scripts/block_headers
@@ -5,7 +5,7 @@
import time, electrum
# 1. start the interface and wait for connection
-interface = electrum.Interface('electrum.no-ip.org:50002:s')
+interface = electrum.Interface('ecdsa.net:50002:s')
interface.start(wait = True)
if not interface.is_connected:
print "not connected"
diff --git a/scripts/peers b/scripts/peers
@@ -1,17 +1,79 @@
#!/usr/bin/env python
-import time, electrum
+import time, electrum, Queue
+from electrum import Interface, SimpleConfig
+from electrum.network import filter_protocol, parse_servers
+from collections import defaultdict
-electrum.set_verbosity(False) # default is True
-network = electrum.Network({'auto_cycle':True})
-network.register_callback('peers',lambda: electrum.print_json(network.irc_servers.keys()))
+# 1. start interface and wait for connection
+interface = electrum.Interface('ecdsa.net:50002:s')
+interface.start(wait = True)
+if not interface.is_connected:
+ print "not connected"
+ exit()
-if not network.start(wait=True):
- print "Not connected"
- exit(1)
+# 2. get list of peers
+q = Queue.Queue()
+interface.send([('server.peers.subscribe',[])], lambda i,x: q.put(x))
+r = q.get(timeout=10000)
+peers = parse_servers(r.get('result'))
+peers = filter_protocol(peers,'s')
-print "Connected to", network.interface.server
-while not network.irc_servers:
- time.sleep(1)
+# start interfaces
+config = SimpleConfig()
+interfaces = map ( lambda server: Interface(server, config), peers )
+results_queue = Queue.Queue()
+reached_servers = []
+for i in interfaces: i.start(q)
+while peers:
+ i = q.get(timeout=1)
+ peers.remove(i.server)
+ if i.is_connected:
+ i.send([('blockchain.headers.subscribe',[])], lambda i,x: results_queue.put((i,x)))
+ reached_servers.append(i.server)
+
+def analyze(results):
+ out = {}
+ dd = {}
+ for k, v in results.items():
+ height = v.get('block_height')
+ merkle = v.get('merkle_root')
+ utxo = v.get('utxo_root')
+ d = dd.get(merkle, defaultdict(int))
+ d[utxo] += 1
+ dd[merkle] = d
+
+ refs = {}
+ for merkle, d in dd.items():
+ v = d.values()
+ m = max(v)
+ ref = d.keys()[v.index(m)]
+ refs[merkle] = ref, m
+
+ for k, v in results.items():
+ height = v.get('block_height')
+ merkle = v.get('merkle_root')
+ utxo = v.get('utxo_root')
+ ref_utxo, num = refs.get(merkle)
+
+ if ref_utxo != utxo and num > 1:
+ out[k] = height, merkle, utxo
+
+ return out
+
+
+results = {}
+while reached_servers:
+ i, r = results_queue.get(timeout=10000)
+ results[i.server] = r.get('result')
+ reached_servers.remove(i.server)
+
+electrum.print_json(results)
+out = analyze(results)
+if out:
+ print "faulty servers:"
+ electrum.print_json(out)
+else:
+ print "ok"
diff --git a/scripts/servers b/scripts/servers
@@ -22,7 +22,7 @@ while servers:
i = q.get(timeout=1000)
servers.remove(i.server)
if i.is_connected:
- i.send([('blockchain.numblocks.subscribe',[])], lambda i,x: results_queue.put((i,x)))
+ i.send([('blockchain.headers.subscribe',[])], lambda i,x: results_queue.put((i,x)))
reached_servers.append(i.server)
i.status = "ok"
else:
@@ -32,7 +32,7 @@ d = defaultdict(int)
while reached_servers:
i, r = results_queue.get(timeout=1000)
- i.blocks = r.get('result')
+ i.blocks = r.get('result').get('block_height')
d[i.blocks] += 1
reached_servers.remove(i.server)