URI: 
       tinitial testnet support (petrkr) - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit d042d6e970389f97331e9caae196bf7a3af1e293
   DIR parent 9138cf3cb8fc899a1779f14b06375cc3d1a2eaa1
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat,  7 Jan 2017 16:58:23 +0100
       
       initial testnet support (petrkr)
       
       Diffstat:
         M electrum                            |       6 +++++-
         M lib/bitcoin.py                      |     109 ++++++++++++++-----------------
         M lib/blockchain.py                   |       3 +++
         M lib/commands.py                     |       5 +++--
         M lib/network.py                      |      14 ++++++++++++--
         M lib/simple_config.py                |       3 +++
         M lib/transaction.py                  |      11 ++++++-----
         M lib/wallet.py                       |       2 +-
       
       8 files changed, 82 insertions(+), 71 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -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
       t@@ -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)
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -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
       t@@ -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]
       t@@ -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
       t@@ -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:]
       t@@ -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:
       t@@ -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:
       t@@ -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('/'):
       t@@ -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):]
       t@@ -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)
        
        
   DIR diff --git a/lib/blockchain.py b/lib/blockchain.py
       t@@ -27,6 +27,7 @@
        
        import os
        import util
       +import bitcoin
        from bitcoin import *
        
        MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
       t@@ -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)
       t@@ -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)
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -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
       t@@ -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')
       t@@ -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],
   DIR diff --git a/lib/network.py b/lib/network.py
       t@@ -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
        
       t@@ -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 = []
       t@@ -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
        
   DIR diff --git a/lib/simple_config.py b/lib/simple_config.py
       t@@ -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):
   DIR diff --git a/lib/transaction.py b/lib/transaction.py
       t@@ -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
       t@@ -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')))
        
        
        
       t@@ -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
        
       t@@ -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
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -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):