#!/usr/bin/env python3
import sys; assert sys.version_info[0] >= 3, "Python 3 required."

from binascii import hexlify, unhexlify
import base58
import hashlib
import hmac
import re
from secp256k1 import PrivateKey, PublicKey

from .zip_0316 import derive_ovks

from ..hd_common import ZCASH_MAIN_COINTYPE, hardened
from ..output import render_args, render_tv
from ..utils import i2beosp


class ExtendedSecretKey:
    def __init__(self, chaincode, sk):
        assert len(chaincode) == 32
        assert isinstance(sk, PrivateKey)

        self.chaincode = chaincode
        self.sk = sk

    @classmethod
    def master(cls, S):
        I = hmac.digest(b'Bitcoin seed', S, 'sha512')
        I_L = I[:32]
        I_R = I[32:]
        sk = PrivateKey(I_L, True)
        return cls(I_R, sk)

    def __bytes__(self):
        # The extra zero byte is specified in
        # <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format>.
        return self.chaincode + b'\x00' + self.sk.private_key

    def public_key(self):
        return ExtendedPublicKey(self.chaincode, self.sk.pubkey)

    def child(self, i):
        assert 0 <= i and i <= 0xFFFFFFFF

        if i >= 0x80000000:
            I = hmac.digest(self.chaincode, b'\x00' + self.sk.private_key + i2beosp(32, i), 'sha512')
        else:
            I = hmac.digest(self.chaincode, self.sk.pubkey.serialize(compressed=True) + i2beosp(32, i), 'sha512')

        I_L = I[:32]
        I_R = I[32:]
        sk_i = PrivateKey(self.sk.tweak_add(I_L), True)
        child_i = self.__class__(I_R, sk_i)

        if i < 0x80000000:
            assert bytes(self.public_key().child(i)) == bytes(child_i.public_key())

        return child_i


class ExtendedPublicKey:
    def __init__(self, chaincode, pk):
        assert len(chaincode) == 32
        assert isinstance(pk, PublicKey)

        self.chaincode = chaincode
        self.pk = pk

    def pubkey_bytes(self):
        pk_bytes = self.pk.serialize(compressed=True)
        assert len(pk_bytes) == 33
        assert pk_bytes[0] in (0x02, 0x03)
        return pk_bytes

    def __bytes__(self):
        return self.chaincode + self.pubkey_bytes()

    def address(self):
        ripemd160 = hashlib.new('ripemd160')
        ripemd160.update(hashlib.sha256(self.pubkey_bytes()).digest())
        return ripemd160.digest()

    def child(self, i):
        assert 0 <= i and i <= 0xFFFFFFFF

        assert i < 0x80000000, "cannot derive a hardened child from a public key"
        I = hmac.digest(self.chaincode, self.pk.serialize(compressed=True) + i2beosp(32, i), 'sha512')
        I_L = I[:32]
        I_R = I[32:]
        pk_i = self.pk.tweak_add(I_L)
        return self.__class__(I_R, pk_i)

    def derive_ovks(self):
        return derive_ovks(self.chaincode, self.pk.serialize(compressed=True))


# Test vectors from <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki>.
BIP32_TEST_VECTORS = [
    {
        'seed': unhexlify("000102030405060708090a0b0c0d0e0f"),
        'path': 'm/0H/1/2H/2/1000000000',
        'vectors': [
            {
                'path': 'm',
                'ext_pub': b'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
                'ext_prv': b'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi',
            },
            {
                'path': 'm/0H',
                'ext_pub': b'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw',
                'ext_prv': b'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7',
            },
            {
                'path': 'm/0H/1',
                'ext_pub': b'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ',
                'ext_prv': b'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs',
            },
            {
                'path': 'm/0H/1/2H',
                'ext_pub': b'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5',
                'ext_prv': b'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM',
            },
            {
                'path': 'm/0H/1/2H/2',
                'ext_pub': b'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV',
                'ext_prv': b'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334',
            },
            {
                'path': 'm/0H/1/2H/2/1000000000',
                'ext_pub': b'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',
                'ext_prv': b'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',
            }
        ]
    },
    {
        'seed': unhexlify("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"),
        'path': 'm/0/2147483647H/1/2147483646H/2',
        'vectors': [
            {
                'path': 'm',
                'ext_pub': b'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB',
                'ext_prv': b'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U',
            },
            {
                'path': 'm/0',
                'ext_pub': b'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH',
                'ext_prv': b'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt',
            },
            {
                'path': 'm/0/2147483647H',
                'ext_pub': b'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a',
                'ext_prv': b'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9',
            },
            {
                'path': 'm/0/2147483647H/1',
                'ext_pub': b'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon',
                'ext_prv': b'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef',
            },
            {
                'path': 'm/0/2147483647H/1/2147483646H',
                'ext_pub': b'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL',
                'ext_prv': b'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc',
            },
            {
                'path': 'm/0/2147483647H/1/2147483646H/2',
                'ext_pub': b'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt',
                'ext_prv': b'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j',
            },
        ]
    },
    # These vectors test for the retention of leading zeros. See bitpay/bitcore-lib#47 and iancoleman/bip39#58 for more information.
    {
        'seed': unhexlify("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"),
        'path': 'm/0H',
        'vectors': [
            {
                'path': 'm',
                'ext_pub': b'xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13',
                'ext_prv': b'xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6',
            },
            {
                'path': 'm/0H',
                'ext_pub': b'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y',
                'ext_prv': b'xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L',
            },
        ]
    },
    # These vectors test for the retention of leading zeros. See btcsuite/btcutil#172 for more information.
    {
        'seed': unhexlify("3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678"),
        'path': 'm/0H/1H',
        'vectors': [
            {
                'path': 'm',
                'ext_pub': b'xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa',
                'ext_prv': b'xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv',
            },
            {
                'path': 'm/0H',
                'ext_pub': b'xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m',
                'ext_prv': b'xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G',
            },
            {
                'path': 'm/0H/1H',
                'ext_pub': b'xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt',
                'ext_prv': b'xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1',
            }
        ]
    }
]

def to_zip32_key_bytes(key_str):
    decoded = base58.b58decode_check(key_str)
    return decoded[13:]

def assert_keys_match(prv, pub, v):
    assert bytes(prv) == to_zip32_key_bytes(v['ext_prv']), (hexlify(bytes(prv)), hexlify(to_zip32_key_bytes(v['ext_prv'])))
    assert bytes(pub) == to_zip32_key_bytes(v['ext_pub']), (hexlify(bytes(pub)), hexlify(to_zip32_key_bytes(v['ext_pub'])))

def verify_test_vectors(obj):
    seed = obj['seed']
    prv = ExtendedSecretKey.master(seed)
    pub = prv.public_key()
    steps = obj['path'].split('/')
    step_pattern = re.compile(r'(\d+)(H?)')
    for step, v in zip(steps, obj['vectors']):
        if step == 'm':
            assert_keys_match(prv, pub, v)
        else:
            step_parts = step_pattern.match(step)
            i = int(step_parts.group(1))
            if len(step_parts.group(2)) > 0:
                i = hardened(i)
            prv = prv.child(i)
            pub = prv.public_key()
            assert_keys_match(prv, pub, v)

def main():
    args = render_args()

    for o in BIP32_TEST_VECTORS:
        verify_test_vectors(o)

    seed = bytes(range(32))
    root_key = ExtendedSecretKey.master(seed)
    purpose_key = root_key.child(hardened(44))
    coin_key = purpose_key.child(hardened(ZCASH_MAIN_COINTYPE))

    test_vectors = []
    for account in range(10):
        account_key = coin_key.child(hardened(account))
        pubkey = account_key.public_key()
        (external_ovk, internal_ovk) = pubkey.derive_ovks()
        test_vectors.append({
            'c' : pubkey.chaincode,
            'pk': pubkey.pk.serialize(compressed=True),
            'address': pubkey.address(),
            'external_ovk': external_ovk,
            'internal_ovk': internal_ovk,
            'account': account,
        })

    render_tv(
        args,
        'bip_0032',
        (
            ('c',            '[u8; 32]'),
            ('pk',           '[u8; 33]'),
            ('address',      '[u8; 20]'),
            ('external_ovk', '[u8; 32]'),
            ('internal_ovk', '[u8; 32]'),
            ('account',      'u32'),
        ),
        test_vectors,
    )

if __file__ == '__main__':
    main()
