/*************************************************
* ElGamal Source File                            *
* (C) 1999-2003 The Botan Project                *
*************************************************/

#include <botan/elgamal.h>
#include <botan/numthry.h>
#include <botan/keypair.h>

namespace Botan {

/*************************************************
* ElGamal_PublicKey Constructor                  *
*************************************************/
ElGamal_PublicKey::ElGamal_PublicKey(const DL_Group& domain,
                                     const BigInt& key) :
   group(domain), y(key),
   powermod_g_p(group.get_g(), group.get_p()), powermod_y_p(y, group.get_p())
   {
   if(y < 0 || y >= group.get_p())
      throw Invalid_Argument("ElGamal: Invalid public parameter");
   }

/*************************************************
* ElGamal Encryption Function                    *
*************************************************/
SecureVector<byte> ElGamal_PublicKey::encrypt(const byte in[],
                                              u32bit length) const
   {
   const BigInt& p = group.get_p();
   BigInt m(in, length);
   if(m >= p)
      throw Invalid_Argument("ElGamal::encrypt: The message is too large");

   BigInt k = random_integer(2 * dl_work_factor(p.bits()), SessionKey);
   BigInt a = powermod_g_p(k);
   BigInt b = powermod_y_p(k);
   b *= m;
   b %= p;

   SecureVector<byte> output(2*p.bytes());
   a.binary_encode(output + (p.bytes() - a.bytes()));
   b.binary_encode(output + output.size() / 2 + (p.bytes() - b.bytes()));
   return output;
   }

/*************************************************
* Return the name of this algorithm              *
*************************************************/
std::string ElGamal_PublicKey::algo_name() const
   {
   return "ElGamal";
   }

/*************************************************
* Return the maximum input size in bits          *
*************************************************/
u32bit ElGamal_PublicKey::max_input_bits() const
   {
   return (group.get_p().bits() - 1);
   }

/*************************************************
* Check Public ElGamal Parameters                *
*************************************************/
bool ElGamal_PublicKey::check_params() const
   {
   if(!group.verify_group())
      return false;
   if(y < 2 || y >= group.get_p())
      return false;
   return true;
   }

/*************************************************
* ElGamal_PrivateKey Constructor                 *
*************************************************/
ElGamal_PrivateKey::ElGamal_PrivateKey(const DL_Group& domain) :
   ElGamal_PublicKey(domain, 0)
   {
   const BigInt& p = group.get_p();
   x = random_integer(2 * dl_work_factor(p.bits()), LongTermKey);
   y = powermod_g_p(x);
   powermod_x_p = FixedExponent_Exp(x, p);
   powermod_y_p = FixedBase_Exp(y, p);

   BigInt k = random_integer(p.bits() - 1, SessionKey);
   blinder.initialize(k, powermod_x_p(k), p);

   if(!check_params())
      throw Self_Test_Failure(algo_name() + " private key generation failed");
   }

/*************************************************
* ElGamal_PrivateKey Constructor                 *
*************************************************/
ElGamal_PrivateKey::ElGamal_PrivateKey(const DL_Group& domain,
                                       const BigInt& priv, const BigInt& pub) :
   ElGamal_PublicKey(domain, pub), x(priv), powermod_x_p(x, group.get_p())
   {
   const BigInt& p = group.get_p();
   BigInt k = random_integer(p.bits() - 1, SessionKey);
   blinder.initialize(k, powermod_x_p(k), p);

   if(y < 1 || x < 1 || y >= group.get_p() || x >= group.get_p())
      throw Invalid_Argument(algo_name() + " private key values are invalid");
   }

/*************************************************
* ElGamal Decryption Function                    *
*************************************************/
SecureVector<byte> ElGamal_PrivateKey::decrypt(const byte in[],
                                               u32bit length) const
   {
   const BigInt& p = group.get_p();
   if(length != 2*p.bytes())
      throw Invalid_Argument("ElGamal::decrypt: Invalid message");

   BigInt a(in, p.bytes());
   BigInt b(in + p.bytes(), p.bytes());

   if(a >= p || b >= p)
      throw Invalid_Argument("ElGamal::decrypt: Invalid message");

   BigInt m = powermod_x_p(blinder.blind(a));
   m = inverse_mod(m, p);
   m *= b;
   m %= p;
   return BigInt::encode(blinder.unblind(m));
   }

/*************************************************
* Check Private ElGamal Parameters               *
*************************************************/
bool ElGamal_PrivateKey::check_params() const
   {
   if(!ElGamal_PublicKey::check_params())
      return false;
   if(x <= 1 || x >= group.get_p())
      return false;
   if(y != powermod_g_p(x))
      return false;

   try {
      KeyPair::check_key(get_pk_encryptor(*this, "EME1(SHA-1)"),
                         get_pk_decryptor(*this, "EME1(SHA-1)")
         );
      }
   catch(Self_Test_Failure)
      {
      return false;
      }

   return true;
   }

}
