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("-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("-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("-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
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,15 +106,34 @@ def print_help_cb(self, opt, value, parser):
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, password=None, args=[]):
|
def run_command(cmd, password=None, args=[]):
|
||||||
|
import xmlrpclib, socket
|
||||||
cmd_runner = Commands(wallet, network)
|
cmd_runner = Commands(wallet, network)
|
||||||
func = getattr(cmd_runner, cmd)
|
func = getattr(cmd_runner, cmd.name)
|
||||||
cmd_runner.password = password
|
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:
|
try:
|
||||||
result = func(*args[1:])
|
result = func(*args[1:])
|
||||||
|
except socket.error:
|
||||||
|
print "Daemon not running"
|
||||||
|
sys.exit(1)
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if cmd.requires_network and not options.offline:
|
||||||
|
if wallet:
|
||||||
|
wallet.stop_threads()
|
||||||
|
|
||||||
|
|
||||||
if type(result) == str:
|
if type(result) == str:
|
||||||
util.print_msg(result)
|
util.print_msg(result)
|
||||||
elif result is not None:
|
elif result is not None:
|
||||||
|
@ -191,15 +211,6 @@ if __name__ == '__main__':
|
||||||
# instanciate wallet for command-line
|
# instanciate wallet for command-line
|
||||||
storage = WalletStorage(config)
|
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 cmd.name in ['create', 'restore']:
|
||||||
if storage.file_exists:
|
if storage.file_exists:
|
||||||
|
@ -215,35 +226,26 @@ if __name__ == '__main__':
|
||||||
if not config.get('server'):
|
if not config.get('server'):
|
||||||
config.set_key('server', pick_random_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)))
|
#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):")
|
#gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
|
||||||
|
#if fee:
|
||||||
if fee:
|
# wallet.set_fee(float(fee)*100000000)
|
||||||
wallet.set_fee(float(fee)*100000000)
|
#if gap:
|
||||||
if gap:
|
# wallet.change_gap_limit(int(gap))
|
||||||
wallet.change_gap_limit(int(gap))
|
|
||||||
|
|
||||||
if cmd.name == 'restore':
|
if cmd.name == 'restore':
|
||||||
import getpass
|
import getpass
|
||||||
seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
|
seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
|
||||||
try:
|
wallet = Wallet.from_seed(str(seed),storage)
|
||||||
seed.decode('hex')
|
if not wallet:
|
||||||
except Exception:
|
sys.exit("Error: Invalid seed")
|
||||||
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.save_seed(password)
|
wallet.save_seed(password)
|
||||||
if not options.offline:
|
if not options.offline:
|
||||||
network = Network(config)
|
network = Network(config)
|
||||||
network.start()
|
network.start()
|
||||||
wallet.start_threads(network)
|
wallet.start_threads(network)
|
||||||
|
|
||||||
print_msg("Recovering wallet...")
|
print_msg("Recovering wallet...")
|
||||||
wallet.restore(lambda x: x)
|
wallet.restore(lambda x: x)
|
||||||
|
|
||||||
if wallet.is_found():
|
if wallet.is_found():
|
||||||
print_msg("Recovery successful")
|
print_msg("Recovery successful")
|
||||||
else:
|
else:
|
||||||
|
@ -253,6 +255,7 @@ if __name__ == '__main__':
|
||||||
print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
|
print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
wallet = Wallet(storage)
|
||||||
wallet.init_seed(None)
|
wallet.init_seed(None)
|
||||||
wallet.save_seed(password)
|
wallet.save_seed(password)
|
||||||
wallet.synchronize()
|
wallet.synchronize()
|
||||||
|
@ -264,6 +267,19 @@ if __name__ == '__main__':
|
||||||
# terminate
|
# terminate
|
||||||
sys.exit(0)
|
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
|
# important warning
|
||||||
if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
|
if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
|
||||||
print_msg("WARNING: ALL your private keys are secret.")
|
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))
|
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
|
||||||
args = args[0:cmd.min_args] + [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)
|
network = Network(config)
|
||||||
if not network.start(wait=True):
|
if not network.start(wait=True):
|
||||||
print_msg("Not connected, aborting.")
|
print_msg("Not connected, aborting.")
|
||||||
sys.exit(1)
|
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:
|
sys.exit(0)
|
||||||
wallet.start_threads(network)
|
|
||||||
wallet.update()
|
|
||||||
else:
|
|
||||||
network = None
|
|
||||||
|
|
||||||
# run the command
|
# run the command
|
||||||
if cmd.name == 'deseed':
|
if cmd.name == 'deseed':
|
||||||
|
@ -394,11 +428,8 @@ if __name__ == '__main__':
|
||||||
wallet.update_password(password, new_password)
|
wallet.update_password(password, new_password)
|
||||||
|
|
||||||
else:
|
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)
|
time.sleep(0.1)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from PyQt4.QtCore import *
|
||||||
import PyQt4.QtCore as QtCore
|
import PyQt4.QtCore as QtCore
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum import Wallet, mnemonic
|
from electrum import Wallet
|
||||||
|
|
||||||
from seed_dialog import SeedDialog
|
from seed_dialog import SeedDialog
|
||||||
from network_dialog import NetworkDialog
|
from network_dialog import NetworkDialog
|
||||||
|
@ -259,13 +259,13 @@ class InstallWizard(QDialog):
|
||||||
if not action:
|
if not action:
|
||||||
return
|
return
|
||||||
|
|
||||||
wallet = Wallet(self.storage)
|
#gap = self.config.get('gap_limit', 5)
|
||||||
gap = self.config.get('gap_limit', 5)
|
#if gap != 5:
|
||||||
if gap != 5:
|
# wallet.gap_limit = gap
|
||||||
wallet.gap_limit = gap
|
# wallet.storage.put('gap_limit', gap, True)
|
||||||
wallet.storage.put('gap_limit', gap, True)
|
|
||||||
|
|
||||||
if action == 'create':
|
if action == 'create':
|
||||||
|
wallet = Wallet(self.storage)
|
||||||
wallet.init_seed(None)
|
wallet.init_seed(None)
|
||||||
if not self.show_seed(wallet):
|
if not self.show_seed(wallet):
|
||||||
return
|
return
|
||||||
|
@ -277,23 +277,14 @@ class InstallWizard(QDialog):
|
||||||
wallet.synchronize() # generate first addresses offline
|
wallet.synchronize() # generate first addresses offline
|
||||||
self.waiting_dialog(create)
|
self.waiting_dialog(create)
|
||||||
|
|
||||||
|
|
||||||
elif action == 'restore':
|
elif action == 'restore':
|
||||||
seed = self.seed_dialog()
|
seed = self.seed_dialog()
|
||||||
if not seed:
|
if not seed:
|
||||||
return
|
return
|
||||||
try:
|
wallet = Wallet.from_seed(seed, self.storage)
|
||||||
wallet.init_seed(seed)
|
|
||||||
except Exception:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc(file=sys.stdout)
|
|
||||||
QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
|
|
||||||
return
|
|
||||||
|
|
||||||
ok, old_password, password = self.password_dialog(wallet)
|
ok, old_password, password = self.password_dialog(wallet)
|
||||||
wallet.save_seed(password)
|
wallet.save_seed(password)
|
||||||
|
|
||||||
|
|
||||||
elif action == 'watching':
|
elif action == 'watching':
|
||||||
mpk = self.mpk_dialog()
|
mpk = self.mpk_dialog()
|
||||||
if not mpk:
|
if not mpk:
|
||||||
|
|
|
@ -426,6 +426,9 @@ class ElectrumWindow(QMainWindow):
|
||||||
raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
|
raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
|
||||||
raw_transaction_text.triggered.connect(self.do_process_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"))
|
help_menu = menubar.addMenu(_("&Help"))
|
||||||
show_about = help_menu.addAction(_("&About"))
|
show_about = help_menu.addAction(_("&About"))
|
||||||
|
@ -1887,6 +1890,18 @@ class ElectrumWindow(QMainWindow):
|
||||||
if tx:
|
if tx:
|
||||||
self.show_transaction(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):
|
def do_process_from_csvReader(self, csvReader):
|
||||||
outputs = []
|
outputs = []
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from version import ELECTRUM_VERSION
|
from version import ELECTRUM_VERSION
|
||||||
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
|
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
|
||||||
from wallet import WalletSynchronizer, WalletStorage
|
from wallet import WalletSynchronizer, WalletStorage
|
||||||
from wallet_factory import WalletFactory as Wallet
|
from wallet import Wallet
|
||||||
from verifier import TxVerifier
|
from verifier import TxVerifier
|
||||||
from network import Network, DEFAULT_SERVERS, DEFAULT_PORTS, pick_random_server
|
from network import Network, DEFAULT_SERVERS, DEFAULT_PORTS, pick_random_server
|
||||||
from interface import Interface
|
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):
|
def Hash(x):
|
||||||
if type(x) is unicode: x=x.encode('utf-8')
|
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_encode = lambda x: x[::-1].encode('hex')
|
||||||
hash_decode = lambda x: x.decode('hex')[::-1]
|
hash_decode = lambda x: x.decode('hex')[::-1]
|
||||||
|
|
||||||
hmac_sha_512 = lambda x,y: hmac.new(x, y, hashlib.sha512).digest()
|
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
|
# pywallet openssl private key implementation
|
||||||
|
|
||||||
|
@ -115,11 +126,11 @@ def i2o_ECPublicKey(pubkey, compressed=False):
|
||||||
def hash_160(public_key):
|
def hash_160(public_key):
|
||||||
try:
|
try:
|
||||||
md = hashlib.new('ripemd160')
|
md = hashlib.new('ripemd160')
|
||||||
md.update(hashlib.sha256(public_key).digest())
|
md.update(sha256(public_key))
|
||||||
return md.digest()
|
return md.digest()
|
||||||
except Exception:
|
except Exception:
|
||||||
import ripemd
|
import ripemd
|
||||||
md = ripemd.new(hashlib.sha256(public_key).digest())
|
md = ripemd.new(sha256(public_key))
|
||||||
return md.digest()
|
return md.digest()
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,15 +148,6 @@ def bc_address_to_hash_160(addr):
|
||||||
bytes = b58decode(addr, 25)
|
bytes = b58decode(addr, 25)
|
||||||
return ord(bytes[0]), bytes[1:21]
|
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'
|
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
__b58base = len(__b58chars)
|
__b58base = len(__b58chars)
|
||||||
|
@ -233,8 +235,7 @@ def regenerate_key(sec):
|
||||||
if not b:
|
if not b:
|
||||||
return False
|
return False
|
||||||
b = b[0:32]
|
b = b[0:32]
|
||||||
secret = int('0x' + b.encode('hex'), 16)
|
return EC_KEY(b)
|
||||||
return EC_KEY(secret)
|
|
||||||
|
|
||||||
def GetPubKey(pubkey, compressed=False):
|
def GetPubKey(pubkey, compressed=False):
|
||||||
return i2o_ECPublicKey(pubkey, compressed)
|
return i2o_ECPublicKey(pubkey, compressed)
|
||||||
|
@ -282,13 +283,14 @@ try:
|
||||||
except Exception:
|
except Exception:
|
||||||
print "cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa"
|
print "cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa"
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
from ecdsa.curves import SECP256k1
|
from ecdsa.curves import SECP256k1
|
||||||
|
from ecdsa.ellipticcurve import Point
|
||||||
from ecdsa.util import string_to_number, number_to_string
|
from ecdsa.util import string_to_number, number_to_string
|
||||||
|
|
||||||
def msg_magic(message):
|
def msg_magic(message):
|
||||||
varint = var_int(len(message))
|
varint = var_int(len(message))
|
||||||
encoded_varint = "".join([chr(int(varint[i:i+2], 16)) for i in xrange(0, len(varint), 2)])
|
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
|
return "\x18Bitcoin Signed Message:\n" + encoded_varint + message
|
||||||
|
|
||||||
|
|
||||||
|
@ -301,9 +303,66 @@ def verify_message(address, signature, message):
|
||||||
return False
|
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):
|
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.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
|
||||||
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
|
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
|
@ -323,10 +382,11 @@ class EC_KEY(object):
|
||||||
else:
|
else:
|
||||||
raise Exception("error: cannot sign message")
|
raise Exception("error: cannot sign message")
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def verify_message(self, address, signature, message):
|
def verify_message(self, address, signature, message):
|
||||||
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
|
""" 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
|
import msqr
|
||||||
curve = curve_secp256k1
|
curve = curve_secp256k1
|
||||||
G = generator_secp256k1
|
G = generator_secp256k1
|
||||||
|
@ -352,7 +412,7 @@ class EC_KEY(object):
|
||||||
beta = msqr.modular_sqrt(alpha, curve.p())
|
beta = msqr.modular_sqrt(alpha, curve.p())
|
||||||
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
|
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
|
||||||
# 1.4 the constructor checks that nR is at infinity
|
# 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:
|
# 1.5 compute e from message:
|
||||||
h = Hash( msg_magic(message) )
|
h = Hash( msg_magic(message) )
|
||||||
e = string_to_number(h)
|
e = string_to_number(h)
|
||||||
|
@ -364,11 +424,94 @@ class EC_KEY(object):
|
||||||
# check that Q is the public key
|
# check that Q is the public key
|
||||||
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
|
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
|
||||||
# check that we get the original signing address
|
# 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:
|
if address != addr:
|
||||||
raise Exception("Bad signature")
|
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 ##############################
|
###################################### BIP32 ##############################
|
||||||
|
|
||||||
random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
|
random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
|
||||||
|
@ -408,7 +551,7 @@ def CKD(k, c, n):
|
||||||
import hmac
|
import hmac
|
||||||
from ecdsa.util import string_to_number, number_to_string
|
from ecdsa.util import string_to_number, number_to_string
|
||||||
order = generator_secp256k1.order()
|
order = generator_secp256k1.order()
|
||||||
keypair = EC_KEY(string_to_number(k))
|
keypair = EC_KEY(k)
|
||||||
K = GetPubKey(keypair.pubkey,True)
|
K = GetPubKey(keypair.pubkey,True)
|
||||||
|
|
||||||
if n & BIP32_PRIME: # We want to make a "secret" address that can't be determined from K
|
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__':
|
if __name__ == '__main__':
|
||||||
test_bip32("000102030405060708090a0b0c0d0e0f", "0'/1/2'/2/1000000000")
|
test_crypto()
|
||||||
test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","0/2147483647'/1/2147483646'/2")
|
#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('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('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('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('getaddressbalance', 1, 1, True, False, 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('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('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('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>')
|
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('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('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('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('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('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)
|
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('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('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._callback = callback
|
||||||
self.password = None
|
self.password = None
|
||||||
|
|
||||||
|
|
||||||
def _run(self, method, args, password_getter):
|
def _run(self, method, args, password_getter):
|
||||||
cmd = known_commands[method]
|
cmd = known_commands[method]
|
||||||
if cmd.requires_password and self.wallet.use_encryption:
|
if cmd.requires_password and self.wallet.use_encryption:
|
||||||
|
@ -117,11 +125,22 @@ class Commands:
|
||||||
apply(self._callback, ())
|
apply(self._callback, ())
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def getaddresshistory(self, addr):
|
def getaddresshistory(self, addr):
|
||||||
assert self.wallet.is_mine(addr)
|
return self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
|
||||||
h = self.wallet.get_history(addr)
|
|
||||||
if h is None: h = self.network.synchronous_get([ ('blockchain.address.get_history',[addr]) ])[0]
|
|
||||||
return h
|
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):
|
def listunspent(self):
|
||||||
import copy
|
import copy
|
||||||
|
@ -129,6 +148,15 @@ class Commands:
|
||||||
for i in l: i["value"] = str(Decimal(i["value"])/100000000)
|
for i in l: i["value"] = str(Decimal(i["value"])/100000000)
|
||||||
return l
|
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):
|
def createrawtransaction(self, inputs, outputs):
|
||||||
# convert to own format
|
# convert to own format
|
||||||
for i in inputs:
|
for i in inputs:
|
||||||
|
@ -199,9 +227,14 @@ class Commands:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def getaddressbalance(self, addr):
|
def getaddressbalance(self, addr):
|
||||||
c, u = self.wallet.get_addr_balance(addr)
|
b = self.network.synchronous_get([ ('blockchain.address.get_balance',[addr]) ])[0]
|
||||||
out = { "confirmed": str(Decimal(c)/100000000) }
|
return str(Decimal(b)/100000000)
|
||||||
if u: out["unconfirmed"] = str(Decimal(u)/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
|
return out
|
||||||
|
|
||||||
def getservers(self):
|
def getservers(self):
|
||||||
|
@ -350,11 +383,27 @@ class Commands:
|
||||||
if cmd.options: print_msg("options:\n" + cmd.options)
|
if cmd.options: print_msg("options:\n" + cmd.options)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getrawtransaction(self, tx_hash):
|
def getrawtransaction(self, tx_hash):
|
||||||
|
import transaction
|
||||||
if self.wallet:
|
if self.wallet:
|
||||||
tx = self.wallet.transactions.get(tx_hash)
|
tx = self.wallet.transactions.get(tx_hash)
|
||||||
if tx:
|
if tx:
|
||||||
return 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)
|
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__":
|
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):
|
def filter_protocol(servers, p):
|
||||||
|
@ -66,6 +97,8 @@ class Network(threading.Thread):
|
||||||
self.interface = None
|
self.interface = None
|
||||||
self.proxy = self.config.get('proxy')
|
self.proxy = self.config.get('proxy')
|
||||||
self.heights = {}
|
self.heights = {}
|
||||||
|
self.merkle_roots = {}
|
||||||
|
self.utxo_roots = {}
|
||||||
self.server_lag = 0
|
self.server_lag = 0
|
||||||
|
|
||||||
dir_path = os.path.join( self.config.path, 'certs')
|
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
|
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):
|
def send_subscriptions(self):
|
||||||
for cb, sub in self.subscriptions.items():
|
for cb, sub in self.subscriptions.items():
|
||||||
self.interface.send(sub, cb)
|
self.interface.send(sub, cb)
|
||||||
|
@ -327,6 +368,8 @@ class Network(threading.Thread):
|
||||||
if not result: return
|
if not result: return
|
||||||
height = result.get('block_height')
|
height = result.get('block_height')
|
||||||
self.heights[i.server] = 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
|
# notify blockchain about the new height
|
||||||
self.blockchain.queue.put((i,result))
|
self.blockchain.queue.put((i,result))
|
||||||
|
|
||||||
|
@ -341,7 +384,7 @@ class Network(threading.Thread):
|
||||||
|
|
||||||
def on_peers(self, i, r):
|
def on_peers(self, i, r):
|
||||||
if not r: return
|
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')
|
self.trigger_callback('peers')
|
||||||
|
|
||||||
def on_banner(self, i, r):
|
def on_banner(self, i, r):
|
||||||
|
@ -356,59 +399,26 @@ class Network(threading.Thread):
|
||||||
|
|
||||||
|
|
||||||
def synchronous_get(self, requests, timeout=100000000):
|
def synchronous_get(self, requests, timeout=100000000):
|
||||||
queue = Queue.Queue()
|
return self.interface.synchronous_get(requests)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def retrieve_transaction(self, tx_hash, tx_height=0):
|
|
||||||
import transaction
|
#def retrieve_transaction(self, tx_hash, tx_height=0):
|
||||||
r = self.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0]
|
# import transaction
|
||||||
if r:
|
# r = self.synchronous_get([ ('blockchain.transaction.get',[tx_hash, tx_height]) ])[0]
|
||||||
return transaction.Transaction(r)
|
# 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
|
ELECTRUM_VERSION = "1.9.8" # version of the client package
|
||||||
PROTOCOL_VERSION = '0.6' # protocol version requested
|
PROTOCOL_VERSION = '0.9' # protocol version requested
|
||||||
SEED_VERSION = 4 # bump this every time the seed generation is modified
|
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
|
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):
|
def __init__(self, config):
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
self.config = config
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.file_exists = False
|
self.file_exists = False
|
||||||
self.path = self.init_path(config)
|
self.path = self.init_path(config)
|
||||||
|
@ -151,7 +152,10 @@ class WalletStorage:
|
||||||
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
|
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
|
||||||
|
|
||||||
|
|
||||||
class Wallet:
|
|
||||||
|
|
||||||
|
|
||||||
|
class NewWallet:
|
||||||
|
|
||||||
def __init__(self, storage):
|
def __init__(self, storage):
|
||||||
|
|
||||||
|
@ -160,7 +164,7 @@ class Wallet:
|
||||||
self.gap_limit_for_change = 3 # constant
|
self.gap_limit_for_change = 3 # constant
|
||||||
|
|
||||||
# saved fields
|
# 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.gap_limit = storage.get('gap_limit', 5)
|
||||||
self.use_change = storage.get('use_change',True)
|
self.use_change = storage.get('use_change',True)
|
||||||
|
@ -180,12 +184,6 @@ class Wallet:
|
||||||
|
|
||||||
self.next_addresses = storage.get('next_addresses',{})
|
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.
|
# This attribute is set when wallet.start_threads is called.
|
||||||
self.synchronizer = None
|
self.synchronizer = None
|
||||||
|
@ -290,41 +288,26 @@ class Wallet:
|
||||||
# we keep only 13 words, that's approximately 139 bits of entropy
|
# we keep only 13 words, that's approximately 139 bits of entropy
|
||||||
words = mnemonic.mn_encode(s)[0:13]
|
words = mnemonic.mn_encode(s)[0:13]
|
||||||
seed = ' '.join(words)
|
seed = ' '.join(words)
|
||||||
if mnemonic_hash(seed).startswith(SEED_PREFIX):
|
if is_seed(seed):
|
||||||
break # this removes 12 bits of entropy
|
break # this will remove 8 bits of entropy
|
||||||
nonce += 1
|
nonce += 1
|
||||||
|
|
||||||
return seed
|
return seed
|
||||||
|
|
||||||
|
|
||||||
def init_seed(self, seed):
|
def init_seed(self, seed):
|
||||||
import mnemonic
|
import mnemonic, unicodedata
|
||||||
|
|
||||||
if self.seed:
|
if self.seed:
|
||||||
raise Exception("a seed exists")
|
raise Exception("a seed exists")
|
||||||
|
|
||||||
|
self.seed_version = NEW_SEED_VERSION
|
||||||
|
|
||||||
if not seed:
|
if not seed:
|
||||||
self.seed = random_seed(128)
|
self.seed = self.make_seed()
|
||||||
self.seed_version = 4
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if seed is hexadecimal
|
self.seed = unicodedata.normalize('NFC', unicode(seed.strip()))
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -338,41 +321,26 @@ class Wallet:
|
||||||
self.create_accounts(password)
|
self.create_accounts(password)
|
||||||
|
|
||||||
|
|
||||||
def create_watching_only_wallet(self, params):
|
def create_watching_only_wallet(self, K0, c0):
|
||||||
K0, c0 = params
|
cK0 = "" #FIXME
|
||||||
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 = ""
|
|
||||||
self.master_public_keys = {
|
self.master_public_keys = {
|
||||||
"m/0'/": (c0, K0, cK0),
|
"m/0'/": (c0, K0, cK0),
|
||||||
}
|
}
|
||||||
self.storage.put('master_public_keys', self.master_public_keys, True)
|
self.storage.put('master_public_keys', self.master_public_keys, True)
|
||||||
self.storage.put('seed_version', self.seed_version, 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):
|
def create_accounts(self, password):
|
||||||
seed = pw_decode(self.seed, 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
|
# create default account
|
||||||
self.create_master_keys('1', password)
|
self.create_master_keys('1of1', password)
|
||||||
self.create_account('1','Main account')
|
self.create_account('1of1','Main account')
|
||||||
|
|
||||||
|
|
||||||
def create_master_keys(self, account_type, password):
|
def create_master_keys(self, account_type, password):
|
||||||
master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
|
master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(password))
|
||||||
if account_type == '1':
|
if account_type == '1of1':
|
||||||
k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
|
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_public_keys["m/0'/"] = (c0, K0, cK0)
|
||||||
self.master_private_keys["m/0'/"] = pw_encode(k0, password)
|
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)
|
self.storage.put('master_private_keys', self.master_private_keys, True)
|
||||||
|
|
||||||
def has_master_public_keys(self, account_type):
|
def has_master_public_keys(self, account_type):
|
||||||
if account_type == '1':
|
if account_type == '1of1':
|
||||||
return "m/0'/" in self.master_public_keys
|
return "m/0'/" in self.master_public_keys
|
||||||
elif account_type == '2of2':
|
elif account_type == '2of2':
|
||||||
return set(["m/1'/", "m/2'/"]) <= set(self.master_public_keys.keys())
|
return set(["m/1'/", "m/2'/"]) <= set(self.master_public_keys.keys())
|
||||||
|
@ -421,17 +389,18 @@ class Wallet:
|
||||||
|
|
||||||
def deseed_branch(self, k):
|
def deseed_branch(self, k):
|
||||||
# check that parent has no seed
|
# check that parent has no seed
|
||||||
assert self.seed == ''
|
# assert self.seed == ''
|
||||||
self.master_private_keys.pop(k)
|
self.master_private_keys.pop(k)
|
||||||
self.storage.put('master_private_keys', self.master_private_keys, True)
|
self.storage.put('master_private_keys', self.master_private_keys, True)
|
||||||
|
|
||||||
|
|
||||||
def is_watching_only(self):
|
def is_watching_only(self):
|
||||||
return (self.seed == '') and (self.master_private_keys == {})
|
return (self.seed == '') and (self.master_private_keys == {})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def account_id(self, account_type, i):
|
def account_id(self, account_type, i):
|
||||||
if account_type == '1':
|
if account_type == '1of1':
|
||||||
return "m/0'/%d"%i
|
return "m/0'/%d"%i
|
||||||
elif account_type == '2of2':
|
elif account_type == '2of2':
|
||||||
return "m/1'/%d & m/2'/%d"%(i,i)
|
return "m/1'/%d & m/2'/%d"%(i,i)
|
||||||
|
@ -451,7 +420,7 @@ class Wallet:
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
|
||||||
def new_account_address(self, account_type = '1'):
|
def new_account_address(self, account_type = '1of1'):
|
||||||
i = self.num_accounts(account_type)
|
i = self.num_accounts(account_type)
|
||||||
k = self.account_id(account_type,i)
|
k = self.account_id(account_type,i)
|
||||||
|
|
||||||
|
@ -465,12 +434,12 @@ class Wallet:
|
||||||
return k, addr
|
return k, addr
|
||||||
|
|
||||||
|
|
||||||
def next_account(self, account_type = '1'):
|
def next_account(self, account_type = '1of1'):
|
||||||
|
|
||||||
i = self.num_accounts(account_type)
|
i = self.num_accounts(account_type)
|
||||||
account_id = self.account_id(account_type,i)
|
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'/"]
|
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)
|
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 })
|
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)
|
k, account = self.next_account(account_type)
|
||||||
if k in self.pending_accounts:
|
if k in self.pending_accounts:
|
||||||
self.pending_accounts.pop(k)
|
self.pending_accounts.pop(k)
|
||||||
|
@ -526,12 +495,6 @@ class Wallet:
|
||||||
self.set_label(k, name)
|
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):
|
def save_accounts(self):
|
||||||
d = {}
|
d = {}
|
||||||
for k, v in self.accounts.items():
|
for k, v in self.accounts.items():
|
||||||
|
@ -596,14 +559,13 @@ class Wallet:
|
||||||
return s[0] == 1
|
return s[0] == 1
|
||||||
|
|
||||||
def get_master_public_key(self):
|
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'/"]
|
c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
|
||||||
return repr((c, K))
|
return repr((c, K))
|
||||||
|
|
||||||
def get_master_private_key(self, account, password):
|
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]
|
master_c, master_K, master_Kc = self.master_public_keys[account]
|
||||||
try:
|
try:
|
||||||
K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
|
K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
|
||||||
|
@ -675,25 +637,14 @@ class Wallet:
|
||||||
return '&'.join(dd)
|
return '&'.join(dd)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_seed(self, password):
|
def get_seed(self, password):
|
||||||
s = pw_decode(self.seed, password)
|
s = pw_decode(self.seed, password)
|
||||||
if self.seed_version == 4:
|
seed = mnemonic_to_seed(s,'').encode('hex')
|
||||||
seed = s
|
|
||||||
self.accounts[0].check_seed(seed)
|
|
||||||
else:
|
|
||||||
seed = mnemonic_hash(s)
|
|
||||||
return seed
|
return seed
|
||||||
|
|
||||||
|
|
||||||
def get_mnemonic(self, password):
|
def get_mnemonic(self, password):
|
||||||
import mnemonic
|
return pw_decode(self.seed, password)
|
||||||
s = pw_decode(self.seed, password)
|
|
||||||
if self.seed_version == 4:
|
|
||||||
return ' '.join(mnemonic.mn_encode(s))
|
|
||||||
else:
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_private_key(self, address, password):
|
def get_private_key(self, address, password):
|
||||||
|
@ -746,23 +697,6 @@ class Wallet:
|
||||||
for txin in tx.inputs:
|
for txin in tx.inputs:
|
||||||
keyid = txin.get('KeyID')
|
keyid = txin.get('KeyID')
|
||||||
if 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 = []
|
roots = []
|
||||||
for s in keyid.split('&'):
|
for s in keyid.split('&'):
|
||||||
m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
|
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):
|
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):
|
if not self.has_master_public_keys(account_type):
|
||||||
continue
|
continue
|
||||||
k, a = self.new_account_address(account_type)
|
k, a = self.new_account_address(account_type)
|
||||||
|
@ -1050,12 +984,6 @@ class Wallet:
|
||||||
|
|
||||||
|
|
||||||
def get_account_name(self, k):
|
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"
|
default = "Unnamed account"
|
||||||
m = re.match("m/0'/(\d+)", k)
|
m = re.match("m/0'/(\d+)", k)
|
||||||
if m:
|
if m:
|
||||||
|
@ -1070,9 +998,9 @@ class Wallet:
|
||||||
num = m.group(1)
|
num = m.group(1)
|
||||||
default = "2of2 account %s"%num
|
default = "2of2 account %s"%num
|
||||||
name = self.labels.get(k, default)
|
name = self.labels.get(k, default)
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def get_account_names(self):
|
def get_account_names(self):
|
||||||
accounts = {}
|
accounts = {}
|
||||||
for k, account in self.accounts.items():
|
for k, account in self.accounts.items():
|
||||||
|
@ -1081,6 +1009,7 @@ class Wallet:
|
||||||
accounts[-1] = 'Imported keys'
|
accounts[-1] = 'Imported keys'
|
||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
def get_account_addresses(self, a, include_change=True):
|
def get_account_addresses(self, a, include_change=True):
|
||||||
if a is None:
|
if a is None:
|
||||||
o = self.addresses(True)
|
o = self.addresses(True)
|
||||||
|
@ -1537,7 +1466,7 @@ class Wallet:
|
||||||
def start_threads(self, network):
|
def start_threads(self, network):
|
||||||
from verifier import TxVerifier
|
from verifier import TxVerifier
|
||||||
self.network = network
|
self.network = network
|
||||||
if self.network:
|
if self.network is not None:
|
||||||
self.verifier = TxVerifier(self.network, self.storage)
|
self.verifier = TxVerifier(self.network, self.storage)
|
||||||
self.verifier.start()
|
self.verifier.start()
|
||||||
self.set_verifier(self.verifier)
|
self.set_verifier(self.verifier)
|
||||||
|
@ -1622,12 +1551,12 @@ class WalletSynchronizer(threading.Thread):
|
||||||
if not self.network.is_connected():
|
if not self.network.is_connected():
|
||||||
self.network.wait_until_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 = []
|
requested_tx = []
|
||||||
missing_tx = []
|
missing_tx = []
|
||||||
|
@ -1657,12 +1586,12 @@ class WalletSynchronizer(threading.Thread):
|
||||||
# request missing transactions
|
# request missing transactions
|
||||||
for tx_hash, tx_height in missing_tx:
|
for tx_hash, tx_height in missing_tx:
|
||||||
if (tx_hash, tx_height) not in requested_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) )
|
requested_tx.append( (tx_hash, tx_height) )
|
||||||
missing_tx = []
|
missing_tx = []
|
||||||
|
|
||||||
# detect if situation has changed
|
# 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():
|
if not self.wallet.is_up_to_date():
|
||||||
self.wallet.set_up_to_date(True)
|
self.wallet.set_up_to_date(True)
|
||||||
self.was_updated = True
|
self.was_updated = True
|
||||||
|
@ -1672,7 +1601,7 @@ class WalletSynchronizer(threading.Thread):
|
||||||
self.was_updated = True
|
self.was_updated = True
|
||||||
|
|
||||||
if self.was_updated:
|
if self.was_updated:
|
||||||
self.wallet.network.trigger_callback('updated')
|
self.network.trigger_callback('updated')
|
||||||
self.was_updated = False
|
self.was_updated = False
|
||||||
|
|
||||||
# 2. get a response
|
# 2. get a response
|
||||||
|
@ -1681,8 +1610,9 @@ class WalletSynchronizer(threading.Thread):
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if interface != self.network.interface:
|
# see if it changed
|
||||||
break
|
#if interface != self.network.interface:
|
||||||
|
# break
|
||||||
|
|
||||||
if not r:
|
if not r:
|
||||||
continue
|
continue
|
||||||
|
@ -1700,7 +1630,7 @@ class WalletSynchronizer(threading.Thread):
|
||||||
addr = params[0]
|
addr = params[0]
|
||||||
if self.wallet.get_status(self.wallet.get_history(addr)) != result:
|
if self.wallet.get_status(self.wallet.get_history(addr)) != result:
|
||||||
if requested_histories.get(addr) is None:
|
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
|
requested_histories[addr] = result
|
||||||
|
|
||||||
elif method == 'blockchain.address.get_history':
|
elif method == 'blockchain.address.get_history':
|
||||||
|
@ -1750,7 +1680,163 @@ class WalletSynchronizer(threading.Thread):
|
||||||
print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
|
print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
|
||||||
|
|
||||||
if self.was_updated and not requested_tx:
|
if self.was_updated and not requested_tx:
|
||||||
self.wallet.network.trigger_callback('updated')
|
self.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
|
# 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
|
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
|
import time, electrum
|
||||||
|
|
||||||
# 1. start the interface and wait for connection
|
# 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)
|
interface.start(wait = True)
|
||||||
if not interface.is_connected:
|
if not interface.is_connected:
|
||||||
print "not connected"
|
print "not connected"
|
||||||
|
|
|
@ -1,17 +1,79 @@
|
||||||
#!/usr/bin/env python
|
#!/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
|
# 1. start interface and wait for connection
|
||||||
network = electrum.Network({'auto_cycle':True})
|
interface = electrum.Interface('ecdsa.net:50002:s')
|
||||||
network.register_callback('peers',lambda: electrum.print_json(network.irc_servers.keys()))
|
interface.start(wait = True)
|
||||||
|
if not interface.is_connected:
|
||||||
|
print "not connected"
|
||||||
|
exit()
|
||||||
|
|
||||||
if not network.start(wait=True):
|
# 2. get list of peers
|
||||||
print "Not connected"
|
q = Queue.Queue()
|
||||||
exit(1)
|
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
|
# start interfaces
|
||||||
while not network.irc_servers:
|
config = SimpleConfig()
|
||||||
time.sleep(1)
|
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)
|
i = q.get(timeout=1000)
|
||||||
servers.remove(i.server)
|
servers.remove(i.server)
|
||||||
if i.is_connected:
|
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)
|
reached_servers.append(i.server)
|
||||||
i.status = "ok"
|
i.status = "ok"
|
||||||
else:
|
else:
|
||||||
|
@ -32,7 +32,7 @@ d = defaultdict(int)
|
||||||
|
|
||||||
while reached_servers:
|
while reached_servers:
|
||||||
i, r = results_queue.get(timeout=1000)
|
i, r = results_queue.get(timeout=1000)
|
||||||
i.blocks = r.get('result')
|
i.blocks = r.get('result').get('block_height')
|
||||||
d[i.blocks] += 1
|
d[i.blocks] += 1
|
||||||
reached_servers.remove(i.server)
|
reached_servers.remove(i.server)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue