text: #ifndef _Modp
  #define _Modp
  
  /* 
   * Currently we only support an MPIR based implementation.
   *
   * What ever is type-def'd to bigint is assumed to have
   * operator overloading for all standard operators, has
   * comparison operations and istream/ostream operators >>/<<.
   *
   * All "integer" operations will be done using operator notation
   * all "modp" operations should be done using the function calls
   * below (interchange with Montgomery arithmetic).
   *
   */
  
  #include "Tools/octetStream.h"
  #include "Tools/random.h"
  
  #include "Math/bigint.h"
  #include "Math/Zp_Data.h"
  
  template<int L>
  class modp_
  { 
    mp_limb_t x[L];
  
    public: 
  
    static const int MAX_N_BITS = 64 * L;
    static const int N_LIMBS = L;
  
    // NEXT FUNCTION IS FOR DEBUG PURPOSES ONLY
    mp_limb_t get_limb(int i) const { return x[i]; }
  
    // use mem* functions instead of mpn_*, so the compiler can optimize
    modp_()
      { avx_memzero(x, sizeof(x)); }
    
    template<int M>
    modp_(const modp_<M>& other)
      {
        inline_mpn_copyi(x, other.get(), min(L, M));
        if (L > M)
          inline_mpn_zero(x + M, L - M);
      }
  
    const mp_limb_t* get() const { return x; }
  
    void assign(const void* buffer, int t) { memcpy(x, buffer, t * sizeof(mp_limb_t)); }
  
    void convert(const mp_limb_t* source, mp_size_t size, const Zp_Data& ZpD,
        bool negative = false);
    void convert_destroy(bigint& source, const Zp_Data& ZpD);
    void convert_destroy(int source, const Zp_Data& ZpD) { to_modp(*this, source, ZpD); }
    template<int M>
    void convert_destroy(const fixint<M>& source, const Zp_Data& ZpD);
  
    void randomize(PRNG& G, const Zp_Data& ZpD);
  
    // Pack and unpack in native format
    //   i.e. Dont care about conversion to human readable form
    //   i.e. When we do montgomery we dont care about decoding
    void pack(octetStream& o,const Zp_Data& ZpD) const;
    void unpack(octetStream& o,const Zp_Data& ZpD);
  
    void pack(octetStream& o) const;
    void unpack(octetStream& o);
  
    bool operator==(const modp_& other) const { return 0 == mpn_cmp(x, other.x, L); }
    bool operator!=(const modp_& other) const { return not (*this == other); }
  
  
     /**********************************
      *         Modp Operations        *
      **********************************/
  
    // Convert representation to and from a modp number
    void to_bigint(bigint& ans,const Zp_Data& ZpD,bool reduce=true) const;
    template<int M>
    void to_bigint(bigint& ans,const Zp_Data& ZpD,bool reduce=true) const;
  
    template<int T>
    void mul(const modp_& x, const modp_& y, const Zp_Data& ZpD);
  
    template<int M> friend void to_modp(modp_<M>& ans,int x,const Zp_Data& ZpD);
    template<int M> friend void to_modp(modp_<M>& ans,const mpz_class& x,const Zp_Data& ZpD);
  
    friend void Add(modp_& ans,const modp_& x,const modp_& y,const Zp_Data& ZpD)
      { ZpD.Add(ans.x, x.x, y.x); }
    template<int M> friend void Sub(modp_<M>& ans,const modp_<M>& x,const modp_<M>& y,const Zp_Data& ZpD);
    template<int M> friend void Mul(modp_<M>& ans,const modp_<M>& x,const modp_<M>& y,const Zp_Data& ZpD);
    template<int M> friend void Sqr(modp_<M>& ans,const modp_<M>& x,const Zp_Data& ZpD);
    template<int M> friend void Negate(modp_<M>& ans,const modp_<M>& x,const Zp_Data& ZpD);
    template<int M> friend void Inv(modp_<M>& ans,const modp_<M>& x,const Zp_Data& ZpD);
     
    template<int M> friend void Power(modp_<M>& ans,const modp_<M>& x,int exp,const Zp_Data& ZpD);
    template<int M> friend void Power(modp_<M>& ans,const modp_<M>& x,const bigint& exp,const Zp_Data& ZpD);
     
    template<int M> friend void assignOne(modp_<M>& x,const Zp_Data& ZpD);
    template<int M> friend void assignZero(modp_<M>& x,const Zp_Data& ZpD);
    template<int M> friend bool isZero(const modp_<M>& x,const Zp_Data& ZpD);
    template<int M> friend bool isOne(const modp_<M>& x,const Zp_Data& ZpD);
    template<int M> friend bool areEqual(const modp_<M>& x,const modp_<M>& y,const Zp_Data& ZpD);
  
    // Input and output from a stream
    //  - Can do in human or machine only format (later should be faster)
    //  - If human output appends a space to help with reading
    //    and also convert back/forth from Montgomery if needed
    void output(ostream& s,const Zp_Data& ZpD,bool human) const;
    void input(istream& s,const Zp_Data& ZpD,bool human);
  
    template<int X, int K>
    friend class gfp_;
  };
  
  typedef modp_<MAX_MOD_SZ> modp;
  
  template<int L>
  inline void modp_<L>::pack(octetStream& o,const Zp_Data& ZpD) const
  {
    o.append((octet*) x,ZpD.t*sizeof(mp_limb_t));
  }
  
  template<int L>
  void assignZero(modp_<L>& x,const Zp_Data& ZpD)
  {
    if (sizeof(x.x) <= 3 * 16)
      // use memset to allow the compiler to optimize
      // if x.x is at most 3*128 bits
      avx_memzero(x.x, sizeof(x.x));
    else
      inline_mpn_zero(x.x, ZpD.get_t());
  }
  
  template<int L>
  inline void Sub(modp_<L>& ans,const modp_<L>& x,const modp_<L>& y,const Zp_Data& ZpD)
  {
    ZpD.Sub(ans.x, x.x, y.x);
  }
  
  template<int L>
  inline void Mul(modp_<L>& ans,const modp_<L>& x,const modp_<L>& y,const Zp_Data& ZpD)
  {
    if (ZpD.montgomery)
      { ZpD.Mont_Mult(ans.x,x.x,y.x); }
    else
      { //ans.x=(x.x*y.x)%ZpD.pr;
        mp_limb_t aa[2*L],q[2*L];
        mpn_mul_n(aa,x.x,y.x,ZpD.t);
        mpn_tdiv_qr(q,ans.x,0,aa,2*ZpD.t,ZpD.prA,ZpD.t);
      }
  }
  
  template<int L>
  template<int T>
  inline void modp_<L>::mul(const modp_<L>& x, const modp_<L>& y, const Zp_Data& ZpD)
  {
    if (ZpD.montgomery)
      ZpD.Mont_Mult_<T>(this->x, x.x, y.x);
    else
      Mul<L>(*this, x, y, ZpD);
  }
  
  template<int L>
  void to_bigint(bigint& ans,const modp_<L>& x,const Zp_Data& ZpD,bool reduce=true)
  {
    x.to_bigint(ans, ZpD, reduce);
  }
  
  #endif
  
  