electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit d042d6e970389f97331e9caae196bf7a3af1e293
parent 9138cf3cb8fc899a1779f14b06375cc3d1a2eaa1
Author: ThomasV <thomasv@electrum.org>
Date:   Sat,  7 Jan 2017 16:58:23 +0100

initial testnet support (petrkr)

Diffstat:
Melectrum | 6+++++-
Mlib/bitcoin.py | 109++++++++++++++++++++++++++++++++++++-------------------------------------------
Mlib/blockchain.py | 3+++
Mlib/commands.py | 5+++--
Mlib/network.py | 14++++++++++++--
Mlib/simple_config.py | 3+++
Mlib/transaction.py | 11++++++-----
Mlib/wallet.py | 2+-
8 files changed, 82 insertions(+), 71 deletions(-)

diff --git a/electrum b/electrum @@ -95,7 +95,7 @@ if is_bundle or is_local or is_android: imp.load_module('electrum', *imp.find_module('lib')) imp.load_module('electrum_gui', *imp.find_module('gui')) - +from electrum import bitcoin, network from electrum import SimpleConfig, Network from electrum.wallet import Wallet from electrum.storage import WalletStorage @@ -324,6 +324,10 @@ if __name__ == '__main__': config = SimpleConfig(config_options) cmdname = config.get('cmd') + if config.get('testnet'): + bitcoin.set_testnet() + network.set_testnet() + # run non-RPC commands separately if cmdname in ['create', 'restore']: run_non_RPC(config) diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -36,6 +36,23 @@ from util import print_error, InvalidPassword import ecdsa import aes +# Bitcoin network constants +TESTNET = False +ADDRTYPE_P2PKH = 0 +ADDRTYPE_P2SH = 5 +XPRV_HEADER = "0488ade4" +XPUB_HEADER = "0488b21e" + +def set_testnet(): + global ADDRTYPE_P2PKH, ADDRTYPE_P2SH + global XPRV_HEADER, XPUB_HEADER + global TESTNET + TESTNET = True + ADDRTYPE_P2PKH = 111 + ADDRTYPE_P2SH = 196 + XPRV_HEADER = "04358394" + XPUB_HEADER = "043587cf" + ################################## transactions FEE_STEP = 10000 @@ -226,11 +243,7 @@ def hash_160(public_key): md.update(sha256(public_key)) return md.digest() -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) - -def hash_160_to_bc_address(h160, addrtype = 0): +def hash_160_to_bc_address(h160, addrtype): vh160 = chr(addrtype) + h160 h = Hash(vh160) addr = vh160 + h[0:4] @@ -240,6 +253,15 @@ def bc_address_to_hash_160(addr): bytes = base_decode(addr, 25, base=58) return ord(bytes[0]), bytes[1:21] +def hash160_to_p2pkh(h160): + return hash_160_to_bc_address(h160, ADDRTYPE_P2PKH) + +def hash160_to_p2sh(h160): + return hash_160_to_bc_address(h160, ADDRTYPE_P2SH) + +def public_key_to_bc_address(public_key): + return hash160_to_p2pkh(hash_160(public_key)) + __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' assert len(__b58chars) == 58 @@ -318,12 +340,14 @@ def PrivKeyToSecret(privkey): return privkey[9:9+32] -def SecretToASecret(secret, compressed=False, addrtype=0): +def SecretToASecret(secret, compressed=False): + addrtype = ADDRTYPE_P2PKH vchIn = chr((addrtype+128)&255) + secret if compressed: vchIn += '\01' return EncodeBase58Check(vchIn) -def ASecretToSecret(key, addrtype=0): +def ASecretToSecret(key): + addrtype = ADDRTYPE_P2PKH vch = DecodeBase58Check(key) if vch and vch[0] == chr((addrtype+128)&255): return vch[1:] @@ -380,19 +404,19 @@ def is_address(addr): addrtype, h = bc_address_to_hash_160(addr) except Exception: return False - if addrtype not in [0, 5]: + if addrtype not in [ADDRTYPE_P2PKH, ADDRTYPE_P2SH]: return False return addr == hash_160_to_bc_address(h, addrtype) def is_p2pkh(addr): if is_address(addr): addrtype, h = bc_address_to_hash_160(addr) - return addrtype in [0] + return addrtype == ADDRTYPE_P2PKH def is_p2sh(addr): if is_address(addr): addrtype, h = bc_address_to_hash_160(addr) - return addrtype in [5] + return addrtype == ADDRTYPE_P2SH def is_private_key(key): try: @@ -702,50 +726,21 @@ def _CKD_pub(cK, c, s): return cK_n, c_n -BITCOIN_HEADER_PRIV = "0488ade4" -BITCOIN_HEADER_PUB = "0488b21e" - -TESTNET_HEADER_PRIV = "04358394" -TESTNET_HEADER_PUB = "043587cf" - -BITCOIN_HEADERS = (BITCOIN_HEADER_PUB, BITCOIN_HEADER_PRIV) -TESTNET_HEADERS = (TESTNET_HEADER_PUB, TESTNET_HEADER_PRIV) - -def _get_headers(testnet): - """Returns the correct headers for either testnet or bitcoin, in the form - of a 2-tuple, like (public, private).""" - if testnet: - return TESTNET_HEADERS - else: - return BITCOIN_HEADERS - - def deserialize_xkey(xkey): - xkey = DecodeBase58Check(xkey) assert len(xkey) == 78 - - xkey_header = xkey[0:4].encode('hex') - # Determine if the key is a bitcoin key or a testnet key. - if xkey_header in TESTNET_HEADERS: - head = TESTNET_HEADER_PRIV - elif xkey_header in BITCOIN_HEADERS: - head = BITCOIN_HEADER_PRIV - else: - raise Exception("Unknown xkey header: '%s'" % xkey_header) - depth = ord(xkey[4]) fingerprint = xkey[5:9] child_number = xkey[9:13] c = xkey[13:13+32] - if xkey[0:4].encode('hex') == head: + if xkey[0:4].encode('hex') == XPRV_HEADER: K_or_k = xkey[13+33:] else: K_or_k = xkey[13+32:] return depth, fingerprint, child_number, c, K_or_k -def get_xkey_name(xkey, testnet=False): +def get_xkey_name(xkey): depth, fingerprint, child_number, c, K = deserialize_xkey(xkey) n = int(child_number.encode('hex'), 16) if n & BIP32_PRIME: @@ -760,38 +755,34 @@ def get_xkey_name(xkey, testnet=False): raise BaseException("xpub depth error") -def xpub_from_xprv(xprv, testnet=False): +def xpub_from_xprv(xprv): depth, fingerprint, child_number, c, k = deserialize_xkey(xprv) K, cK = get_pubkeys_from_secret(k) - header_pub, _ = _get_headers(testnet) - xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK + xpub = XPUB_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + cK return EncodeBase58Check(xpub) -def bip32_root(seed, testnet=False): - header_pub, header_priv = _get_headers(testnet) +def bip32_root(seed): I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest() master_k = I[0:32] master_c = I[32:] K, cK = get_pubkeys_from_secret(master_k) - xprv = (header_priv + "00" + "00000000" + "00000000").decode("hex") + master_c + chr(0) + master_k - xpub = (header_pub + "00" + "00000000" + "00000000").decode("hex") + master_c + cK + xprv = (XPRV_HEADER + "00" + "00000000" + "00000000").decode("hex") + master_c + chr(0) + master_k + xpub = (XPUB_HEADER + "00" + "00000000" + "00000000").decode("hex") + master_c + cK return EncodeBase58Check(xprv), EncodeBase58Check(xpub) -def xpub_from_pubkey(cK, testnet=False): - header_pub, header_priv = _get_headers(testnet) +def xpub_from_pubkey(cK): assert cK[0] in ['\x02','\x03'] master_c = chr(0)*32 - xpub = (header_pub + "00" + "00000000" + "00000000").decode("hex") + master_c + cK + xpub = (XPUB_HEADER + "00" + "00000000" + "00000000").decode("hex") + master_c + cK return EncodeBase58Check(xpub) -def bip32_private_derivation(xprv, branch, sequence, testnet=False): +def bip32_private_derivation(xprv, branch, sequence): assert sequence.startswith(branch) if branch == sequence: - return xprv, xpub_from_xprv(xprv, testnet) - header_pub, header_priv = _get_headers(testnet) + return xprv, xpub_from_xprv(xprv) depth, fingerprint, child_number, c, k = deserialize_xkey(xprv) sequence = sequence[len(branch):] for n in sequence.split('/'): @@ -805,13 +796,12 @@ def bip32_private_derivation(xprv, branch, sequence, testnet=False): fingerprint = hash_160(parent_cK)[0:4] child_number = ("%08X"%i).decode('hex') K, cK = get_pubkeys_from_secret(k) - xprv = header_priv.decode('hex') + chr(depth) + fingerprint + child_number + c + chr(0) + k - xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK + xprv = XPRV_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + chr(0) + k + xpub = XPUB_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + cK return EncodeBase58Check(xprv), EncodeBase58Check(xpub) -def bip32_public_derivation(xpub, branch, sequence, testnet=False): - header_pub, _ = _get_headers(testnet) +def bip32_public_derivation(xpub, branch, sequence): depth, fingerprint, child_number, c, cK = deserialize_xkey(xpub) assert sequence.startswith(branch) sequence = sequence[len(branch):] @@ -821,10 +811,9 @@ def bip32_public_derivation(xpub, branch, sequence, testnet=False): parent_cK = cK cK, c = CKD_pub(cK, c, i) depth += 1 - fingerprint = hash_160(parent_cK)[0:4] child_number = ("%08X"%i).decode('hex') - xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK + xpub = XPUB_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + cK return EncodeBase58Check(xpub) diff --git a/lib/blockchain.py b/lib/blockchain.py @@ -27,6 +27,7 @@ import os import util +import bitcoin from bitcoin import * MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 @@ -51,6 +52,7 @@ class Blockchain(util.PrintError): def verify_header(self, header, prev_header, bits, target): prev_hash = self.hash_header(prev_header) assert prev_hash == header.get('prev_block_hash'), "prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')) + if bitcoin.TESTNET: return assert bits == header.get('bits'), "bits mismatch: %s vs %s" % (bits, header.get('bits')) _hash = self.hash_header(header) assert int('0x' + _hash, 16) <= target, "insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target) @@ -109,6 +111,7 @@ class Blockchain(util.PrintError): if os.path.exists(filename): return try: + if bitcoin.TESTNET: raise import urllib, socket socket.setdefaulttimeout(30) self.print_error("downloading ", self.headers_url) diff --git a/lib/commands.py b/lib/commands.py @@ -38,7 +38,7 @@ from decimal import Decimal import util from util import print_msg, format_satoshis, print_stderr import bitcoin -from bitcoin import is_address, hash_160_to_bc_address, hash_160, COIN, TYPE_ADDRESS +from bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from transaction import Transaction import paymentrequest from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED @@ -251,7 +251,7 @@ class Commands: """Create multisig address""" assert isinstance(pubkeys, list), (type(num), type(pubkeys)) redeem_script = Transaction.multisig_script(pubkeys, num) - address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) + address = bitcoin.hash160_to_p2sh(hash_160(redeem_script.decode('hex'))) return {'address':address, 'redeemScript':redeem_script} @command('w') @@ -746,6 +746,7 @@ def get_parser(): group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory") group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") + group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") # create main parser parser = argparse.ArgumentParser( parents=[parent_parser], diff --git a/lib/network.py b/lib/network.py @@ -64,6 +64,14 @@ DEFAULT_SERVERS = { 'btc.mustyoshi.com':{'t':'50001', 's':'50002'}, } +def set_testnet(): + global DEFAULT_PORTS, DEFAULT_SERVERS + DEFAULT_PORTS = {'t':'51001', 's':'51002'} + DEFAULT_SERVERS = { + '14.3.140.101': DEFAULT_PORTS, + 'testnet.not.fyi': DEFAULT_PORTS + } + NODES_RETRY_INTERVAL = 60 SERVER_RETRY_INTERVAL = 10 @@ -99,7 +107,7 @@ def parse_servers(result): return servers -def filter_protocol(hostmap = DEFAULT_SERVERS, protocol = 's'): +def filter_protocol(hostmap, protocol = 's'): '''Filters the hostmap for those implementing protocol. The result is a list in serialized form.''' eligible = [] @@ -109,7 +117,9 @@ def filter_protocol(hostmap = DEFAULT_SERVERS, protocol = 's'): eligible.append(serialize_server(host, port, protocol)) return eligible -def pick_random_server(hostmap = DEFAULT_SERVERS, protocol = 's', exclude_set = set()): +def pick_random_server(hostmap = None, protocol = 's', exclude_set = set()): + if hostmap is None: + hostmap = DEFAULT_SERVERS eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set) return random.choice(eligible) if eligible else None diff --git a/lib/simple_config.py b/lib/simple_config.py @@ -76,6 +76,9 @@ class SimpleConfig(PrintError): if path is None: path = self.user_dir() + if self.get('testnet'): + path = os.path.join(path, 'testnet') + # Make directory if it does not yet exist. if not os.path.exists(path): if os.path.islink(path): diff --git a/lib/transaction.py b/lib/transaction.py @@ -30,6 +30,7 @@ import bitcoin from bitcoin import * +from bitcoin import hash160_to_p2sh, hash160_to_p2pkh from util import print_error, profiler import time import sys @@ -359,7 +360,7 @@ def parse_scriptSig(d, bytes): d['x_pubkeys'] = x_pubkeys d['pubkeys'] = pubkeys d['redeemScript'] = redeemScript - d['address'] = hash_160_to_bc_address(hash_160(redeemScript.decode('hex')), 5) + d['address'] = hash160_to_p2sh(hash_160(redeemScript.decode('hex'))) @@ -377,12 +378,12 @@ def get_address_from_output_script(bytes): # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash_160_to_bc_address(decoded[2][1]) + return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1]) # p2sh match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] if match_decoded(decoded, match): - return TYPE_ADDRESS, hash_160_to_bc_address(decoded[1][1],5) + return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1]) return TYPE_SCRIPT, bytes @@ -540,11 +541,11 @@ class Transaction: return addr.encode('hex') elif output_type == TYPE_ADDRESS: addrtype, hash_160 = bc_address_to_hash_160(addr) - if addrtype == 0: + if addrtype == bitcoin.ADDRTYPE_P2PKH: script = '76a9' # op_dup, op_hash_160 script += push_script(hash_160.encode('hex')) script += '88ac' # op_equalverify, op_checksig - elif addrtype == 5: + elif addrtype == bitcoin.ADDRTYPE_P2SH: script = 'a9' # op_hash_160 script += push_script(hash_160.encode('hex')) script += '87' # op_equal diff --git a/lib/wallet.py b/lib/wallet.py @@ -1633,7 +1633,7 @@ class Multisig_Wallet(Deterministic_Wallet): def pubkeys_to_address(self, pubkeys): redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m) - address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) + address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), bitcoin.ADDRTYPE_P2SH) return address def new_pubkeys(self, c, i):