#
#  SelfTest/PublicKey/test_ElGamal.py: Self-test for the ElGamal primitive
#
# ===================================================================
# The contents of this file are dedicated to the public domain.  To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================

"""Self-test suite for Crypto.PublicKey.ElGamal"""

__revision__ = "$Id$"


import pytest

from Crypto import Random
from Crypto.PublicKey import ElGamal
from Crypto.SelfTest.st_common import a2b_hex
from Crypto.Util.number import bytes_to_long


class TestElGamal:
    def test_component_types(self):
        tv = self.tve[0]
        p, g, y, x = (int(tv[c], 16) for c in ("p", "g", "y", "x"))
        key = ElGamal.construct((p, g, y, x))
        for value, expected in ((key.p, p), (key.g, g), (key.y, y), (key.x, x)):
            assert type(value) is int
            assert value == expected
        assert key.has_private()

        pub = key.publickey()
        assert not pub.has_private()
        with pytest.raises(AttributeError):
            _ = pub.x
        assert pub.p == p

    #
    # Test vectors
    #
    # There seem to be no real ElGamal test vectors available in the
    # public domain. The following test vectors have been generated
    # with libgcrypt 1.5.0.
    #
    # Encryption
    tve = [
        {
            # 256 bits
            "p": "BA4CAEAAED8CBE952AFD2126C63EB3B345D65C2A0A73D2A3AD4138B6D09BD933",
            "g": "05",
            "y": "60D063600ECED7C7C55146020E7A31C4476E9793BEAED420FEC9E77604CAE4EF",
            "x": "1D391BA2EE3C37FE1BA175A69B2C73A11238AD77675932",
            "k": "F5893C5BAB4131264066F57AB3D8AD89E391A0B68A68A1",
            "pt": "48656C6C6F207468657265",
            "ct1": "32BFD5F487966CEA9E9356715788C491EC515E4ED48B58F0F00971E93AAA5EC7",
            "ct2": "7BE8FBFF317C93E82FCEF9BD515284BA506603FEA25D01C0CB874A31F315EE68",
        },
        {
            # 512 bits
            "p": "F1B18AE9F7B4E08FDA9A04832F4E919D89462FD31BF12F92791A93519F75076D6CE3942689CDFF2F344CAFF0F82D01864F69F3AECF566C774CBACF728B81A227",
            "g": "07",
            "y": "688628C676E4F05D630E1BE39D0066178CA7AA83836B645DE5ADD359B4825A12B02EF4252E4E6FA9BEC1DB0BE90F6D7C8629CABB6E531F472B2664868156E20C",
            "x": "14E60B1BDFD33436C0DA8A22FDC14A2CCDBBED0627CE68",
            "k": "38DBF14E1F319BDA9BAB33EEEADCAF6B2EA5250577ACE7",
            "pt": "48656C6C6F207468657265",
            "ct1": "290F8530C2CC312EC46178724F196F308AD4C523CEABB001FACB0506BFED676083FE0F27AC688B5C749AB3CB8A80CD6F7094DBA421FB19442F5A413E06A9772B",
            "ct2": "1D69AAAD1DC50493FB1B8E8721D621D683F3BF1321BE21BC4A43E11B40C9D4D9C80DE3AAC2AB60D31782B16B61112E68220889D53C4C3136EE6F6CE61F8A23A0",
        },
    ]

    # Signature
    tvs = [
        {
            # 256 bits
            "p": "D2F3C41EA66530838A704A48FFAC9334F4701ECE3A97CEE4C69DD01AE7129DD7",
            "g": "05",
            "y": "C3F9417DC0DAFEA6A05C1D2333B7A95E63B3F4F28CC962254B3256984D1012E7",
            "x": "165E4A39BE44D5A2D8B1332D416BC559616F536BC735BB",
            "k": "C7F0C794A7EAD726E25A47FF8928013680E73C51DD3D7D99BFDA8F492585928F",
            "h": "48656C6C6F207468657265",
            "sig1": "35CA98133779E2073EF31165AFCDEB764DD54E96ADE851715495F9C635E1E7C2",
            "sig2": "0135B88B1151279FE5D8078D4FC685EE81177EE9802AB123A73925FC1CB059A7",
        },
        {
            # 512 bits
            "p": "E24CF3A4B8A6AF749DCA6D714282FE4AABEEE44A53BB6ED15FBE32B5D3C3EF9CC4124A2ECA331F3C1C1B667ACA3766825217E7B5F9856648D95F05330C6A19CF",
            "g": "0B",
            "y": "2AD3A1049CA5D4ED207B2431C79A8719BB4073D4A94E450EA6CEE8A760EB07ADB67C0D52C275EE85D7B52789061EE45F2F37D9B2AE522A51C28329766BFE68AC",
            "x": "16CBB4F46D9ECCF24FF9F7E63CAA3BD8936341555062AB",
            "k": "8A3D89A4E429FD2476D7D717251FB79BF900FFE77444E6BB8299DC3F84D0DD57ABAB50732AE158EA52F5B9E7D8813E81FD9F79470AE22F8F1CF9AEC820A78C69",
            "h": "48656C6C6F207468657265",
            "sig1": "BE001AABAFFF976EC9016198FBFEA14CBEF96B000CCC0063D3324016F9E91FE80D8F9325812ED24DDB2B4D4CF4430B169880B3CE88313B53255BD4EC0378586F",
            "sig2": "5E266F3F837BA204E3BBB6DBECC0611429D96F8C7CE8F4EFDF9D4CB681C2A954468A357BF4242CEC7418B51DFC081BCD21299EF5B5A0DDEF3A139A1817503DDE",
        },
    ]

    def test_generate_180(self):
        self._test_random_key(180)

    @pytest.mark.parametrize("tv", tve)
    def test_encryption(self, tv):
        d = self.convert_tv(tv, True)
        key = ElGamal.construct(d["key"])
        ct = key._encrypt(d["pt"], d["k"])
        assert ct[0] == d["ct1"]
        assert ct[1] == d["ct2"]

    @pytest.mark.parametrize("tv", tve)
    def test_decryption(self, tv):
        d = self.convert_tv(tv, True)
        key = ElGamal.construct(d["key"])
        pt = key._decrypt((d["ct1"], d["ct2"]))
        assert pt == d["pt"]

    @pytest.mark.parametrize("tv", tvs)
    def test_signing(self, tv):
        d = self.convert_tv(tv, True)
        key = ElGamal.construct(d["key"])
        sig1, sig2 = key._sign(d["h"], d["k"])
        assert sig1 == d["sig1"]
        assert sig2 == d["sig2"]

    @pytest.mark.parametrize("tv", tvs)
    def test_verification(self, tv):
        d = self.convert_tv(tv, True)
        key = ElGamal.construct(d["key"])
        # Positive test
        res = key._verify(d["h"], (d["sig1"], d["sig2"]))
        assert res
        # Negative test
        res = key._verify(d["h"], (d["sig1"] + 1, d["sig2"]))
        assert not res

    def test_bad_key3(self):
        tup = tup0 = list(self.convert_tv(self.tvs[0], 1)["key"])[:3]
        tup[0] += 1  # p += 1 (not prime)
        with pytest.raises(ValueError):
            ElGamal.construct(tup)

        tup = tup0
        tup[1] = 1  # g = 1
        with pytest.raises(ValueError):
            ElGamal.construct(tup)

        tup = tup0
        tup[2] = tup[0] * 2  # y = 2*p
        with pytest.raises(ValueError):
            ElGamal.construct(tup)

    def test_bad_key4(self):
        tup = list(self.convert_tv(self.tvs[0], 1)["key"])
        tup[3] += 1  # x += 1
        with pytest.raises(ValueError):
            ElGamal.construct(tup)

    def convert_tv(self, tv, as_longs=0):
        """Convert a test vector from textual form (hexadecimal ascii
        to either integers or byte strings."""
        key_comps = "p", "g", "y", "x"
        tv2 = {}
        for c in tv:
            tv2[c] = a2b_hex(tv[c])
            if as_longs or c in key_comps or c in ("sig1", "sig2"):
                tv2[c] = bytes_to_long(tv2[c])
        tv2["key"] = []
        for c in key_comps:
            tv2["key"] += [tv2[c]]
            del tv2[c]
        return tv2

    def _test_random_key(self, bits):
        elgObj = ElGamal.generate(bits, Random.new().read)
        self._check_private_key(elgObj)
        self._exercise_primitive(elgObj)
        pub = elgObj.publickey()
        self._check_public_key(pub)
        self._exercise_public_primitive(elgObj)

    def _check_private_key(self, elgObj):
        # Check capabilities
        assert elgObj.has_private()

        # Sanity check key data
        assert 1 < elgObj.g < (elgObj.p - 1)
        assert pow(elgObj.g, elgObj.p - 1, elgObj.p) == 1
        assert 1 < elgObj.x < (elgObj.p - 1)
        assert pow(elgObj.g, elgObj.x, elgObj.p) == elgObj.y

    def _check_public_key(self, elgObj):
        # Check capabilities
        assert not elgObj.has_private()

        # Sanity check key data
        assert 1 < elgObj.g < (elgObj.p - 1)
        assert pow(elgObj.g, elgObj.p - 1, elgObj.p) == 1

    def _exercise_primitive(self, elgObj):
        # Test encryption/decryption
        plaintext = 127218
        ciphertext = elgObj._encrypt(plaintext, 123456789)
        plaintextP = elgObj._decrypt(ciphertext)
        assert plaintext == plaintextP

        # Test signature/verification
        signature = elgObj._sign(plaintext, 987654321)
        elgObj._verify(plaintext, signature)

    def _exercise_public_primitive(self, elgObj):
        plaintext = 92987276
        elgObj._encrypt(plaintext, 123456789)
