public IBigInteger ModPow(
            IBigInteger exponent,
            IBigInteger m)
        {
            if (m.SignValue < 1)
                throw new ArithmeticException("Modulus must be positive");

            if (m.Equals(One))
                return Zero;

            if (exponent.SignValue == 0)
                return One;

            if (SignValue == 0)
                return Zero;

            int[] zVal = null;
            int[] yAccum = null;
            int[] yVal;

            // Montgomery exponentiation is only possible if the modulus is odd,
            // but AFAIK, this is always the case for crypto algo's
            bool useMonty = ((m.Magnitude[m.Magnitude.Length - 1] & 1) == 1);
            long mQ = 0;
            if (useMonty)
            {
                mQ = m.GetMQuote();

                // tmp = this * R mod m
                IBigInteger tmp = ShiftLeft(32*m.Magnitude.Length).Mod(m);
                zVal = tmp.Magnitude;

                useMonty = (zVal.Length <= m.Magnitude.Length);

                if (useMonty)
                {
                    yAccum = new int[m.Magnitude.Length + 1];
                    if (zVal.Length < m.Magnitude.Length)
                    {
                        var longZ = new int[m.Magnitude.Length];
                        zVal.CopyTo(longZ, longZ.Length - zVal.Length);
                        zVal = longZ;
                    }
                }
            }

            if (!useMonty)
            {
                if (Magnitude.Length <= m.Magnitude.Length)
                {
                    //zAccum = new int[m.Magnitude.Length * 2];
                    zVal = new int[m.Magnitude.Length];
                    Magnitude.CopyTo(zVal, zVal.Length - Magnitude.Length);
                }
                else
                {
                    //
                    // in normal practice we'll never see this...
                    //
                    IBigInteger tmp = Remainder(m);

                    //zAccum = new int[m.Magnitude.Length * 2];
                    zVal = new int[m.Magnitude.Length];
                    tmp.Magnitude.CopyTo(zVal, zVal.Length - tmp.Magnitude.Length);
                }

                yAccum = new int[m.Magnitude.Length*2];
            }

            yVal = new int[m.Magnitude.Length];

            //
            // from LSW to MSW
            //
            for (int i = 0; i < exponent.Magnitude.Length; i++)
            {
                int v = exponent.Magnitude[i];
                int bits = 0;

                if (i == 0)
                {
                    while (v > 0)
                    {
                        v <<= 1;
                        bits++;
                    }

                    //
                    // first time in initialise y
                    //
                    zVal.CopyTo(yVal, 0);

                    v <<= 1;
                    bits++;
                }

                while (v != 0)
                {
                    if (useMonty)
                    {
                        // Montgomery square algo doesn't exist, and a normal
                        // square followed by a Montgomery reduction proved to
                        // be almost as heavy as a Montgomery mulitply.
                        MultiplyMonty(yAccum, yVal, yVal, m.Magnitude, mQ);
                    }
                    else
                    {
                        Square(yAccum, yVal);
                        Remainder(yAccum, m.Magnitude);
                        Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, yVal.Length);
                        ZeroOut(yAccum);
                    }
                    bits++;

                    if (v < 0)
                    {
                        if (useMonty)
                        {
                            MultiplyMonty(yAccum, yVal, zVal, m.Magnitude, mQ);
                        }
                        else
                        {
                            Multiply(yAccum, yVal, zVal);
                            Remainder(yAccum, m.Magnitude);
                            Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0,
                                       yVal.Length);
                            ZeroOut(yAccum);
                        }
                    }

                    v <<= 1;
                }

                while (bits < 32)
                {
                    if (useMonty)
                    {
                        MultiplyMonty(yAccum, yVal, yVal, m.Magnitude, mQ);
                    }
                    else
                    {
                        Square(yAccum, yVal);
                        Remainder(yAccum, m.Magnitude);
                        Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, yVal.Length);
                        ZeroOut(yAccum);
                    }
                    bits++;
                }
            }

            if (useMonty)
            {
                // Return y * R^(-1) mod m by doing y * 1 * R^(-1) mod m
                ZeroOut(zVal);
                zVal[zVal.Length - 1] = 1;
                MultiplyMonty(yAccum, yVal, zVal, m.Magnitude, mQ);
            }

            IBigInteger result = new BigInteger(1, yVal, true);

            return exponent.SignValue > 0
                       ? result
                       : result.ModInverse(m);
        }