#
#   ElGamal.py : ElGamal encryption/decryption and signatures
# 
#  Part of the Python Cryptography Toolkit, version 1.1
# 
# Distribute and use freely; there are no restrictions on further 
# dissemination and usage except those imposed by the laws of your 
# country of residence.  This software is provided "as is" without
# warranty of fitness for use or suitability for any purpose, express
# or implied. Use at your own risk or not at all. 
# 

from pubkey import *

error = 'ElGamal module'

# Generate an ElGamal key with N bits
def generate(bits, randfunc, progress_func=None):
    obj=ElGamalobj()
    # Generate prime p
    if progress_func: apply(progress_func, ('p\n',))
    obj.p=bignum(getPrime(bits, randfunc))
    # Generate random number g
    if progress_func: apply(progress_func, ('g\n',))
    size=bits-1-(ord(randfunc(1)) & 63) # g will be from 1--64 bits smaller than p
    if size<1: size=bits-1
    while (1):
        obj.g=bignum(getPrime(size, randfunc))
        if obj.g<obj.p: break
        size=(size+1) % bits
        if size==0: size=4
    # Generate random number x
    if progress_func: apply(progress_func, ('x\n',))
    while (1):
        size=bits-1-ord(randfunc(1)) # x will be from 1 to 256 bits smaller than p
        if size>2: break
    while (1):
        obj.x=bignum(getPrime(size, randfunc))
        if obj.x<obj.p: break
        size=(size+1) % bits
        if size==0: size=4
    if progress_func: apply(progress_func, ('y\n',))
    obj.y=pow(obj.g, obj.x, obj.p)
    return obj
    
def construct(tuple):
    obj=ElGamalobj()
    if len(tuple) not in [3,4]:
        raise error, 'argument for construct() wrong length' 
    for i in range(len(tuple)):
	field = obj.keydata[i]
	setattr(obj, field, tuple[i])
    return obj
    
class ElGamalobj(pubkey):
    keydata=['p', 'g', 'y', 'x']

    def _encrypt(self, M, K):
        a=pow(self.g, K, self.p)
        b=( M*pow(self.y, K, self.p) ) % self.p
	return ( a,b )
    def _decrypt(self, M):
	if (not hasattr(self, 'x')):
	    raise error, 'Private key not available in this object'
        ax=pow(M[0], self.x, self.p)
        plaintext=(M[1] * inverse(ax, self.p ) ) % self.p
	return plaintext
    def _sign(self, M, K):
	if (not hasattr(self, 'x')):
	    raise error, 'Private key not available in this object'
        p1=self.p-1
        if (GCD(K, p1)!=1):
            raise error, 'Bad K value: GCD(K,p-1)!=1'
        a=pow(self.g, K, self.p)
        t=(M-self.x*a) % p1
        while t<0: t=t+p1
        b=(t*inverse(K, p1)) % p1
        return (a, b)
    def _verify(self, M, sig):
        v1=pow(self.y, sig[0], self.p)
        v1=(v1*pow(sig[0], sig[1], self.p)) % self.p
        v2=pow(self.g, M, self.p)
        if v1==v2: return 1
        return 0
        
    def size(self):
	"Return the maximum number of bits that can be handled by this key."
        bits, power = 0,1L
	while (power<self.p): bits, power = bits+1, power<<1
	return bits-1
	
    def hasprivate(self):
	"""Return a Boolean denoting whether the object contains
	private components."""
	if hasattr(self, 'x'): return 1
	else: return 0

    def publickey(self):
	"""Return a new key object containing only the public information."""
        return construct((self.p, self.g, self.y))

        
object=ElGamalobj
