Merge branch '2.0'
This commit is contained in:
commit
7267579fe0
113
electrum
113
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:
|
||||
|
||||
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_error("Connected to " + network.interface.connection_msg)
|
||||
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")
|
||||
|
||||
if wallet:
|
||||
wallet.start_threads(network)
|
||||
wallet.update()
|
||||
else:
|
||||
network = None
|
||||
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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
218
lib/bitcoin.py
218
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")
|
||||
|
||||
|
|
|
@ -68,8 +68,8 @@ register_command('freeze', 1, 1, False, True, True, 'Freeze the
|
|||
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('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]
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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__":
|
||||
|
||||
|
|
106
lib/network.py
106
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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
326
lib/wallet.py
326
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,41 +288,26 @@ 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")
|
||||
|
||||
self.seed_version = NEW_SEED_VERSION
|
||||
|
||||
if not seed:
|
||||
self.seed = random_seed(128)
|
||||
self.seed_version = 4
|
||||
self.seed = self.make_seed()
|
||||
return
|
||||
|
||||
# check if seed is hexadecimal
|
||||
seed = seed.strip()
|
||||
try:
|
||||
assert seed
|
||||
seed.decode('hex')
|
||||
self.seed_version = 4
|
||||
self.seed = str(seed)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
words = seed.split()
|
||||
self.seed_version = 4
|
||||
self.seed = mnemonic.mn_decode(words)
|
||||
|
||||
if not self.seed:
|
||||
raise Exception("Invalid seed")
|
||||
self.seed = unicodedata.normalize('NFC', unicode(seed.strip()))
|
||||
|
||||
|
||||
|
||||
|
@ -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')
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
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,12 +984,6 @@ class Wallet:
|
|||
|
||||
|
||||
def get_account_name(self, k):
|
||||
if k == 0:
|
||||
if self.seed_version == 4:
|
||||
name = 'Main account'
|
||||
else:
|
||||
name = 'Old account'
|
||||
else:
|
||||
default = "Unnamed account"
|
||||
m = re.match("m/0'/(\d+)", k)
|
||||
if m:
|
||||
|
@ -1070,9 +998,9 @@ class Wallet:
|
|||
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
|
||||
|
|
|
@ -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)
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue