tMerge pull request #741 from chrisglass/clean-support-testnet - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 508d8a798f7bf7605d3b220386e042e62882614d
 (DIR) parent 8feb2a7b672ce9075fadb056d1f2f5320376795b
 (HTM) Author: ThomasV <thomasv1@gmx.de>
       Date:   Fri, 27 Jun 2014 16:23:29 +0200
       
       Merge pull request #741 from chrisglass/clean-support-testnet
       
       Made all bip32 primitives testnet compatible.
       Diffstat:
         M lib/bitcoin.py                      |      59 +++++++++++++++++++++++--------
         M lib/tests/test_bitcoin.py           |      39 +++++++++++++++++++++++++------
       
       2 files changed, 76 insertions(+), 22 deletions(-)
       ---
 (DIR) diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -640,23 +640,50 @@ 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
       -    assert xkey[0:4].encode('hex') in ["0488ade4", "0488b21e"]
       +
       +    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') == "0488ade4":
       +    if xkey[0:4].encode('hex') == head:
                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):
       +def get_xkey_name(xkey, testnet=False):
            depth, fingerprint, child_number, c, K = deserialize_xkey(xkey)
            n = int(child_number.encode('hex'), 16)
            if n & BIP32_PRIME:
       t@@ -671,27 +698,29 @@ def get_xkey_name(xkey):
                raise BaseException("xpub depth error")
        
        
       -def xpub_from_xprv(xprv):
       +def xpub_from_xprv(xprv, testnet=False):
            depth, fingerprint, child_number, c, k = deserialize_xkey(xprv)
            K, cK = get_pubkeys_from_secret(k)
       -    xpub = "0488B21E".decode('hex') + chr(depth) + fingerprint + child_number + c + cK
       +    header_pub, _  = _get_headers(testnet)
       +    xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK
            return EncodeBase58Check(xpub)
        
        
       -def bip32_root(seed):
       +def bip32_root(seed, testnet=False):
            import hmac
       +    header_pub, header_priv = _get_headers(testnet)
            seed = seed.decode('hex')
            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 = ("0488ADE4" + "00" + "00000000" + "00000000").decode("hex") + master_c + chr(0) + master_k
       -    xpub = ("0488B21E" + "00" + "00000000" + "00000000").decode("hex") + master_c + cK
       +    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
            return EncodeBase58Check(xprv), EncodeBase58Check(xpub)
        
        
       -
       -def bip32_private_derivation(xprv, branch, sequence):
       +def bip32_private_derivation(xprv, branch, sequence, testnet=False):
       +    header_pub, header_priv = _get_headers(testnet)
            depth, fingerprint, child_number, c, k = deserialize_xkey(xprv)
            assert sequence.startswith(branch)
            sequence = sequence[len(branch):]
       t@@ -706,13 +735,13 @@ def bip32_private_derivation(xprv, branch, sequence):
            fingerprint = hash_160(parent_cK)[0:4]
            child_number = ("%08X"%i).decode('hex')
            K, cK = get_pubkeys_from_secret(k)
       -    xprv = "0488ADE4".decode('hex') + chr(depth) + fingerprint + child_number + c + chr(0) + k
       -    xpub = "0488B21E".decode('hex') + chr(depth) + fingerprint + child_number + c + cK
       +    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
            return EncodeBase58Check(xprv), EncodeBase58Check(xpub)
        
        
       -
       -def bip32_public_derivation(xpub, branch, sequence):
       +def bip32_public_derivation(xpub, branch, sequence, testnet=False):
       +    header_pub, _ = _get_headers(testnet)
            depth, fingerprint, child_number, c, cK = deserialize_xkey(xpub)
            assert sequence.startswith(branch)
            sequence = sequence[len(branch):]
       t@@ -725,7 +754,7 @@ def bip32_public_derivation(xpub, branch, sequence):
        
            fingerprint = hash_160(parent_cK)[0:4]
            child_number = ("%08X"%i).decode('hex')
       -    xpub = "0488B21E".decode('hex') + chr(depth) + fingerprint + child_number + c + cK
       +    xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK
            return EncodeBase58Check(xpub)
        
        
 (DIR) diff --git a/lib/tests/test_bitcoin.py b/lib/tests/test_bitcoin.py
       t@@ -6,7 +6,7 @@ from lib.bitcoin import (
            generator_secp256k1, point_to_ser, public_key_to_bc_address, EC_KEY,
            bip32_root, bip32_public_derivation, bip32_private_derivation, pw_encode,
            pw_decode, Hash, public_key_from_private_key, address_from_private_key,
       -    is_valid, is_private_key, mnemonic_to_seed)
       +    is_valid, is_private_key, mnemonic_to_seed, xpub_from_xprv)
        
        try:
            import ecdsa
       t@@ -49,24 +49,33 @@ class Test_bitcoin(unittest.TestCase):
        
            def test_bip32(self):
                # see https://en.bitcoin.it/wiki/BIP_0032_TestVectors
       -        xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000")
       +        xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000", testnet=False)
                assert xpub == "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
                assert xprv == "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
        
       -        xpub, xprv = self._do_test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","m/0/2147483647'/1/2147483646'/2")
       +        xpub, xprv = self._do_test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","m/0/2147483647'/1/2147483646'/2", testnet=False)
                assert xpub == "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
                assert xprv == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
        
       -    def _do_test_bip32(self, seed, sequence):
       -        xprv, xpub = bip32_root(seed)
       +    def test_bip32_testnet(self):
       +        xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000", testnet=True)
       +        assert xpub == "tpubDHNy3kAG39ThyiwwsgoKY4iRenXDRtce8qdCFJZXPMCJg5dsCUHayp84raLTpvyiNA9sXPob5rgqkKvkN8S7MMyXbnEhGJMW64Cf4vFAoaF"
       +        assert xprv == "tprv8kgvuL81tmn36Fv9z38j8f4K5m1HGZRjZY2QxnXDy5PuqbP6a5TzoKWCgTcGHBu66W3TgSbAu2yX6sPza5FkHmy564Sh6gmCPUNeUt4yj2x"
       +
       +        xpub, xprv = self._do_test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","m/0/2147483647'/1/2147483646'/2", testnet=True)
       +        assert xpub == "tpubDG9qJLc8hq8PMG7y4sQEodLSocEkfj4mGrUC75b7G76mDoqybcUXvmvRsruvLeF14mhixobZwZP6LwqeFePKU83Sv8ZnxWdHBb6VzE6zbvC"
       +        assert xprv == "tprv8jTo9vZtZTSiTo6BBDjeQDgLEaipWPsrhYsQpZYoqqJNPKbCyDewkHJZhkoSHiWYCUf1Gm4TFzQxcG4D6s1J9Hsn4whDK7QYyHHokJeUuac"
       +
       +    def _do_test_bip32(self, seed, sequence, testnet):
       +        xprv, xpub = bip32_root(seed, testnet)
                assert sequence[0:2] == "m/"
                path = 'm'
                sequence = sequence[2:]
                for n in sequence.split('/'):
                    child_path = path + '/' + n
                    if n[-1] != "'":
       -                xpub2 = bip32_public_derivation(xpub, path, child_path)
       -            xprv, xpub = bip32_private_derivation(xprv, path, child_path)
       +                xpub2 = bip32_public_derivation(xpub, path, child_path, testnet)
       +            xprv, xpub = bip32_private_derivation(xprv, path, child_path, testnet)
                    if n[-1] != "'":
                        assert xpub == xpub2
                    path = child_path
       t@@ -109,6 +118,22 @@ class Test_bitcoin(unittest.TestCase):
                result = Hash(payload)
                self.assertEqual(expected, result)
        
       +    def test_xpub_from_xprv(self):
       +        """We can derive the xpub key from a xprv."""
       +        # Taken from test vectors in https://en.bitcoin.it/wiki/BIP_0032_TestVectors
       +        xpub = "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
       +        xprv = "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
       +
       +        result = xpub_from_xprv(xprv)
       +        self.assertEqual(result, xpub)
       +
       +    def test_xpub_from_xprv_testnet(self):
       +        """We can derive the xpub key from a xprv using testnet headers."""
       +        xpub = "tpubDHNy3kAG39ThyiwwsgoKY4iRenXDRtce8qdCFJZXPMCJg5dsCUHayp84raLTpvyiNA9sXPob5rgqkKvkN8S7MMyXbnEhGJMW64Cf4vFAoaF"
       +        xprv = "tprv8kgvuL81tmn36Fv9z38j8f4K5m1HGZRjZY2QxnXDy5PuqbP6a5TzoKWCgTcGHBu66W3TgSbAu2yX6sPza5FkHmy564Sh6gmCPUNeUt4yj2x"
       +        result = xpub_from_xprv(xprv, testnet=True)
       +        self.assertEqual(result, xpub)
       +
        
        class Test_keyImport(unittest.TestCase):
            """ The keys used in this class are TEST keys from