using System;
using System.Collections;
using System.Collections.Generic;
using Renci.SshNet.Security.Org.BouncyCastle.Math.EC.Abc;
using Renci.SshNet.Security.Org.BouncyCastle.Math.EC.Endo;
using Renci.SshNet.Security.Org.BouncyCastle.Math.EC.Multiplier;
using Renci.SshNet.Security.Org.BouncyCastle.Math.Field;
using Renci.SshNet.Security.Org.BouncyCastle.Math.Raw;
using Renci.SshNet.Security.Org.BouncyCastle.Utilities;

namespace Renci.SshNet.Security.Org.BouncyCastle.Math.EC
{
    /// <remarks>Base class for an elliptic curve.</remarks>
    internal abstract class ECCurve
    {
        public const int COORD_AFFINE = 0;
        public const int COORD_HOMOGENEOUS = 1;
        public const int COORD_JACOBIAN = 2;
        public const int COORD_JACOBIAN_CHUDNOVSKY = 3;
        public const int COORD_JACOBIAN_MODIFIED = 4;
        public const int COORD_LAMBDA_AFFINE = 5;
        public const int COORD_LAMBDA_PROJECTIVE = 6;
        public const int COORD_SKEWED = 7;

        public static int[] GetAllCoordinateSystems()
        {
            return new int[]{ COORD_AFFINE, COORD_HOMOGENEOUS, COORD_JACOBIAN, COORD_JACOBIAN_CHUDNOVSKY,
                COORD_JACOBIAN_MODIFIED, COORD_LAMBDA_AFFINE, COORD_LAMBDA_PROJECTIVE, COORD_SKEWED };
        }

        internal class Config
        {
            protected ECCurve outer;
            protected int coord;
            protected ECEndomorphism endomorphism;
            protected ECMultiplier multiplier;

            internal Config(ECCurve outer, int coord, ECEndomorphism endomorphism, ECMultiplier multiplier)
            {
                this.outer = outer;
                this.coord = coord;
                this.endomorphism = endomorphism;
                this.multiplier = multiplier;
            }

            public Config SetCoordinateSystem(int coord)
            {
                this.coord = coord;
                return this;
            }

            public Config SetEndomorphism(ECEndomorphism endomorphism)
            {
                this.endomorphism = endomorphism;
                return this;
            }

            public Config SetMultiplier(ECMultiplier multiplier)
            {
                this.multiplier = multiplier;
                return this;
            }

            public ECCurve Create()
            {
                if (!outer.SupportsCoordinateSystem(coord))
                {
                    throw new InvalidOperationException("unsupported coordinate system");
                }

                ECCurve c = outer.CloneCurve();
                if (c == outer)
                {
                    throw new InvalidOperationException("implementation returned current curve");
                }

                c.m_coord = coord;
                c.m_endomorphism = endomorphism;
                c.m_multiplier = multiplier;

                return c;
            }
        }

        protected readonly IFiniteField m_field;
        protected ECFieldElement m_a, m_b;
        protected BigInteger m_order, m_cofactor;

        protected int m_coord = COORD_AFFINE;
        protected ECEndomorphism m_endomorphism = null;
        protected ECMultiplier m_multiplier = null;

        protected ECCurve(IFiniteField field)
        {
            this.m_field = field;
        }

        public abstract int FieldSize { get; }
        public abstract ECFieldElement FromBigInteger(BigInteger x);
        public abstract bool IsValidFieldElement(BigInteger x);

        public virtual Config Configure()
        {
            return new Config(this, this.m_coord, this.m_endomorphism, this.m_multiplier);
        }

        public virtual ECPoint ValidatePoint(BigInteger x, BigInteger y)
        {
            ECPoint p = CreatePoint(x, y);
            if (!p.IsValid())
            {
                throw new ArgumentException("Invalid point coordinates");
            }
            return p;
        }

        public virtual ECPoint ValidatePoint(BigInteger x, BigInteger y, bool withCompression)
        {
            ECPoint p = CreatePoint(x, y, withCompression);
            if (!p.IsValid())
            {
                throw new ArgumentException("Invalid point coordinates");
            }
            return p;
        }

        public virtual ECPoint CreatePoint(BigInteger x, BigInteger y)
        {
            return CreatePoint(x, y, false);
        }

        public virtual ECPoint CreatePoint(BigInteger x, BigInteger y, bool withCompression)
        {
            return CreateRawPoint(FromBigInteger(x), FromBigInteger(y), withCompression);
        }

        protected abstract ECCurve CloneCurve();

        protected internal abstract ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression);

        protected internal abstract ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression);

        protected virtual ECMultiplier CreateDefaultMultiplier()
        {
            GlvEndomorphism glvEndomorphism = m_endomorphism as GlvEndomorphism;
            if (glvEndomorphism != null)
            {
                return new GlvMultiplier(this, glvEndomorphism);
            }

            return new WNafL2RMultiplier();
        }

        public virtual bool SupportsCoordinateSystem(int coord)
        {
            return coord == COORD_AFFINE;
        }

        public virtual PreCompInfo GetPreCompInfo(ECPoint point, string name)
        {
            CheckPoint(point);

            IDictionary table;
            lock (point)
            {
                table = point.m_preCompTable;
            }

            if (null == table)
                return null;

            lock (table)
            {
                return (PreCompInfo)table[name];
            }
        }

        /**
         * Compute a <code>PreCompInfo</code> for a point on this curve, under a given name. Used by
         * <code>ECMultiplier</code>s to save the precomputation for this <code>ECPoint</code> for use
         * by subsequent multiplication.
         * 
         * @param point
         *            The <code>ECPoint</code> to store precomputations for.
         * @param name
         *            A <code>String</code> used to index precomputations of different types.
         * @param callback
         *            Called to calculate the <code>PreCompInfo</code>.
         */
        public virtual PreCompInfo Precompute(ECPoint point, string name, IPreCompCallback callback)
        {
            CheckPoint(point);

            IDictionary table;
            lock (point)
            {
                table = point.m_preCompTable;
                if (null == table)
                {
                    point.m_preCompTable = table = new Dictionary<object, object>(4);
                }
            }

            lock (table)
            {
                PreCompInfo existing = (PreCompInfo)table[name];
                PreCompInfo result = callback.Precompute(existing);

                if (result != existing)
                {
                    table[name] = result;
                }

                return result;
            }
        }

        public virtual ECPoint ImportPoint(ECPoint p)
        {
            if (this == p.Curve)
            {
                return p;
            }
            if (p.IsInfinity)
            {
                return Infinity;
            }

            // TODO Default behaviour could be improved if the two curves have the same coordinate system by copying any Z coordinates.
            p = p.Normalize();

            return CreatePoint(p.XCoord.ToBigInteger(), p.YCoord.ToBigInteger(), p.IsCompressed);
        }

        /**
         * Normalization ensures that any projective coordinate is 1, and therefore that the x, y
         * coordinates reflect those of the equivalent point in an affine coordinate system. Where more
         * than one point is to be normalized, this method will generally be more efficient than
         * normalizing each point separately.
         * 
         * @param points
         *            An array of points that will be updated in place with their normalized versions,
         *            where necessary
         */
        public virtual void NormalizeAll(ECPoint[] points)
        {
            NormalizeAll(points, 0, points.Length, null);
        }

        /**
         * Normalization ensures that any projective coordinate is 1, and therefore that the x, y
         * coordinates reflect those of the equivalent point in an affine coordinate system. Where more
         * than one point is to be normalized, this method will generally be more efficient than
         * normalizing each point separately. An (optional) z-scaling factor can be applied; effectively
         * each z coordinate is scaled by this value prior to normalization (but only one
         * actual multiplication is needed).
         * 
         * @param points
         *            An array of points that will be updated in place with their normalized versions,
         *            where necessary
         * @param off
         *            The start of the range of points to normalize
         * @param len
         *            The length of the range of points to normalize
         * @param iso
         *            The (optional) z-scaling factor - can be null
         */
        public virtual void NormalizeAll(ECPoint[] points, int off, int len, ECFieldElement iso)
        {
            CheckPoints(points, off, len);

            switch (this.CoordinateSystem)
            {
                case ECCurve.COORD_AFFINE:
                case ECCurve.COORD_LAMBDA_AFFINE:
                {
                    if (iso != null)
                        throw new ArgumentException("not valid for affine coordinates", "iso");

                    return;
                }
            }

            /*
             * Figure out which of the points actually need to be normalized
             */
            ECFieldElement[] zs = new ECFieldElement[len];
            int[] indices = new int[len];
            int count = 0;
            for (int i = 0; i < len; ++i)
            {
                ECPoint p = points[off + i];
                if (null != p && (iso != null || !p.IsNormalized()))
                {
                    zs[count] = p.GetZCoord(0);
                    indices[count++] = off + i;
                }
            }

            if (count == 0)
            {
                return;
            }

            ECAlgorithms.MontgomeryTrick(zs, 0, count, iso);

            for (int j = 0; j < count; ++j)
            {
                int index = indices[j];
                points[index] = points[index].Normalize(zs[j]);
            }
        }

        public abstract ECPoint Infinity { get; }

        public virtual IFiniteField Field
        {
            get { return m_field; }
        }

        public virtual ECFieldElement A
        {
            get { return m_a; }
        }

        public virtual ECFieldElement B
        {
            get { return m_b; }
        }

        public virtual BigInteger Order
        {
            get { return m_order; }
        }

        public virtual BigInteger Cofactor
        {
            get { return m_cofactor; }
        }

        public virtual int CoordinateSystem
        {
            get { return m_coord; }
        }

        /**
         * Create a cache-safe lookup table for the specified sequence of points. All the points MUST
         * belong to this <code>ECCurve</code> instance, and MUST already be normalized.
         */
        public virtual ECLookupTable CreateCacheSafeLookupTable(ECPoint[] points, int off, int len)
        {
            int FE_BYTES = (FieldSize + 7) / 8;
            byte[] table = new byte[len * FE_BYTES * 2];
            {
                int pos = 0;
                for (int i = 0; i < len; ++i)
                {
                    ECPoint p = points[off + i];
                    byte[] px = p.RawXCoord.ToBigInteger().ToByteArray();
                    byte[] py = p.RawYCoord.ToBigInteger().ToByteArray();

                    int pxStart = px.Length > FE_BYTES ? 1 : 0, pxLen = px.Length - pxStart;
                    int pyStart = py.Length > FE_BYTES ? 1 : 0, pyLen = py.Length - pyStart;

                    Array.Copy(px, pxStart, table, pos + FE_BYTES - pxLen, pxLen); pos += FE_BYTES;
                    Array.Copy(py, pyStart, table, pos + FE_BYTES - pyLen, pyLen); pos += FE_BYTES;
                }
            }

            return new DefaultLookupTable(this, table, len);
        }

        protected virtual void CheckPoint(ECPoint point)
        {
            if (null == point || (this != point.Curve))
                throw new ArgumentException("must be non-null and on this curve", "point");
        }

        protected virtual void CheckPoints(ECPoint[] points)
        {
            CheckPoints(points, 0, points.Length);
        }

        protected virtual void CheckPoints(ECPoint[] points, int off, int len)
        {
            if (points == null)
                throw new ArgumentNullException("points");
            if (off < 0 || len < 0 || (off > (points.Length - len)))
                throw new ArgumentException("invalid range specified", "points");

            for (int i = 0; i < len; ++i)
            {
                ECPoint point = points[off + i];
                if (null != point && this != point.Curve)
                    throw new ArgumentException("entries must be null or on this curve", "points");
            }
        }

        public virtual bool Equals(ECCurve other)
        {
            if (this == other)
                return true;
            if (null == other)
                return false;
            return Field.Equals(other.Field)
                && A.ToBigInteger().Equals(other.A.ToBigInteger())
                && B.ToBigInteger().Equals(other.B.ToBigInteger());
        }

        public override bool Equals(object obj) 
        {
            return Equals(obj as ECCurve);
        }

        public override int GetHashCode()
        {
            return Field.GetHashCode()
                ^ Integers.RotateLeft(A.ToBigInteger().GetHashCode(), 8)
                ^ Integers.RotateLeft(B.ToBigInteger().GetHashCode(), 16);
        }

        protected abstract ECPoint DecompressPoint(int yTilde, BigInteger X1);

        public virtual ECEndomorphism GetEndomorphism()
        {
            return m_endomorphism;
        }

        /**
         * Sets the default <code>ECMultiplier</code>, unless already set. 
         */
        public virtual ECMultiplier GetMultiplier()
        {
            lock (this)
            {
                if (this.m_multiplier == null)
                {
                    this.m_multiplier = CreateDefaultMultiplier();
                }
                return this.m_multiplier;
            }
        }

        /**
         * Decode a point on this curve from its ASN.1 encoding. The different
         * encodings are taken account of, including point compression for
         * <code>F<sub>p</sub></code> (X9.62 s 4.2.1 pg 17).
         * @return The decoded point.
         */
        public virtual ECPoint DecodePoint(byte[] encoded)
        {
            ECPoint p = null;
            int expectedLength = (FieldSize + 7) / 8;

            byte type = encoded[0];
            switch (type)
            {
                case 0x00: // infinity
                {
                    if (encoded.Length != 1)
                        throw new ArgumentException("Incorrect length for infinity encoding", "encoded");

                    p = Infinity;
                    break;
                }

                case 0x02: // compressed
                case 0x03: // compressed
                {
                    if (encoded.Length != (expectedLength + 1))
                        throw new ArgumentException("Incorrect length for compressed encoding", "encoded");

                    int yTilde = type & 1;
                    BigInteger X = new BigInteger(1, encoded, 1, expectedLength);

                    p = DecompressPoint(yTilde, X);
                    if (!p.ImplIsValid(true, true))
                        throw new ArgumentException("Invalid point");

                    break;
                }

                case 0x04: // uncompressed
                {
                    if (encoded.Length != (2 * expectedLength + 1))
                        throw new ArgumentException("Incorrect length for uncompressed encoding", "encoded");

                    BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
                    BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength);

                    p = ValidatePoint(X, Y);
                    break;
                }

                case 0x06: // hybrid
                case 0x07: // hybrid
                {
                    if (encoded.Length != (2 * expectedLength + 1))
                        throw new ArgumentException("Incorrect length for hybrid encoding", "encoded");

                    BigInteger X = new BigInteger(1, encoded, 1, expectedLength);
                    BigInteger Y = new BigInteger(1, encoded, 1 + expectedLength, expectedLength);

                    if (Y.TestBit(0) != (type == 0x07))
                        throw new ArgumentException("Inconsistent Y coordinate in hybrid encoding", "encoded");

                    p = ValidatePoint(X, Y);
                    break;
                }

                default:
                    throw new FormatException("Invalid point encoding " + type);
            }

            if (type != 0x00 && p.IsInfinity)
                throw new ArgumentException("Invalid infinity encoding", "encoded");

            return p;
        }

        private class DefaultLookupTable
            : ECLookupTable
        {
            private readonly ECCurve m_outer;
            private readonly byte[] m_table;
            private readonly int m_size;

            internal DefaultLookupTable(ECCurve outer, byte[] table, int size)
            {
                this.m_outer = outer;
                this.m_table = table;
                this.m_size = size;
            }

            public virtual int Size
            {
                get { return m_size; }
            }

            public virtual ECPoint Lookup(int index)
            {
                int FE_BYTES = (m_outer.FieldSize + 7) / 8;
                byte[] x = new byte[FE_BYTES], y = new byte[FE_BYTES];
                int pos = 0;

                for (int i = 0; i < m_size; ++i)
                {
                    byte MASK = (byte)(((i ^ index) - 1) >> 31);

                    for (int j = 0; j < FE_BYTES; ++j)
                    {
                        x[j] ^= (byte)(m_table[pos + j] & MASK);
                        y[j] ^= (byte)(m_table[pos + FE_BYTES + j] & MASK);
                    }

                    pos += (FE_BYTES * 2);
                }

                ECFieldElement X = m_outer.FromBigInteger(new BigInteger(1, x));
                ECFieldElement Y = m_outer.FromBigInteger(new BigInteger(1, y));
                return m_outer.CreateRawPoint(X, Y, false);
            }
        }
    }

    internal abstract class AbstractFpCurve
        : ECCurve
    {
        protected AbstractFpCurve(BigInteger q)
            : base(FiniteFields.GetPrimeField(q))
        {
        }

        public override bool IsValidFieldElement(BigInteger x)
        {
            return x != null && x.SignValue >= 0 && x.CompareTo(Field.Characteristic) < 0;
        }

        protected override ECPoint DecompressPoint(int yTilde, BigInteger X1)
        {
            ECFieldElement x = FromBigInteger(X1);
            ECFieldElement rhs = x.Square().Add(A).Multiply(x).Add(B);
            ECFieldElement y = rhs.Sqrt();

            /*
             * If y is not a square, then we haven't got a point on the curve
             */
            if (y == null)
                throw new ArgumentException("Invalid point compression");

            if (y.TestBitZero() != (yTilde == 1))
            {
                // Use the other root
                y = y.Negate();
            }

            return CreateRawPoint(x, y, true);
        }
    }

    /**
     * Elliptic curve over Fp
     */
    internal class FpCurve
        : AbstractFpCurve
    {
        private const int FP_DEFAULT_COORDS = COORD_JACOBIAN_MODIFIED;

        protected readonly BigInteger m_q, m_r;
        protected readonly FpPoint m_infinity;

        public FpCurve(BigInteger q, BigInteger a, BigInteger b)
            : this(q, a, b, null, null)
        {
        }

        public FpCurve(BigInteger q, BigInteger a, BigInteger b, BigInteger order, BigInteger cofactor)
            : base(q)
        {
            this.m_q = q;
            this.m_r = FpFieldElement.CalculateResidue(q);
            this.m_infinity = new FpPoint(this, null, null, false);

            this.m_a = FromBigInteger(a);
            this.m_b = FromBigInteger(b);
            this.m_order = order;
            this.m_cofactor = cofactor;
            this.m_coord = FP_DEFAULT_COORDS;
        }

        protected FpCurve(BigInteger q, BigInteger r, ECFieldElement a, ECFieldElement b)
            : this(q, r, a, b, null, null)
        {
        }

        protected FpCurve(BigInteger q, BigInteger r, ECFieldElement a, ECFieldElement b, BigInteger order, BigInteger cofactor)
            : base(q)
        {
            this.m_q = q;
            this.m_r = r;
            this.m_infinity = new FpPoint(this, null, null, false);

            this.m_a = a;
            this.m_b = b;
            this.m_order = order;
            this.m_cofactor = cofactor;
            this.m_coord = FP_DEFAULT_COORDS;
        }

        protected override ECCurve CloneCurve()
        {
            return new FpCurve(m_q, m_r, m_a, m_b, m_order, m_cofactor);
        }

        public override bool SupportsCoordinateSystem(int coord)
        {
            switch (coord)
            {
                case COORD_AFFINE:
                case COORD_HOMOGENEOUS:
                case COORD_JACOBIAN:
                case COORD_JACOBIAN_MODIFIED:
                    return true;
                default:
                    return false;
            }
        }

        public virtual BigInteger Q
        {
            get { return m_q; }
        }

        public override ECPoint Infinity
        {
            get { return m_infinity; }
        }

        public override int FieldSize
        {
            get { return m_q.BitLength; }
        }

        public override ECFieldElement FromBigInteger(BigInteger x)
        {
            return new FpFieldElement(this.m_q, this.m_r, x);
        }

        protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression)
        {
            return new FpPoint(this, x, y, withCompression);
        }

        protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression)
        {
            return new FpPoint(this, x, y, zs, withCompression);
        }

        public override ECPoint ImportPoint(ECPoint p)
        {
            if (this != p.Curve && this.CoordinateSystem == COORD_JACOBIAN && !p.IsInfinity)
            {
                switch (p.Curve.CoordinateSystem)
                {
                    case COORD_JACOBIAN:
                    case COORD_JACOBIAN_CHUDNOVSKY:
                    case COORD_JACOBIAN_MODIFIED:
                        return new FpPoint(this,
                            FromBigInteger(p.RawXCoord.ToBigInteger()),
                            FromBigInteger(p.RawYCoord.ToBigInteger()),
                            new ECFieldElement[] { FromBigInteger(p.GetZCoord(0).ToBigInteger()) },
                            p.IsCompressed);
                    default:
                        break;
                }
            }

            return base.ImportPoint(p);
        }
    }

    internal abstract class AbstractF2mCurve
        : ECCurve
    {
        public static BigInteger Inverse(int m, int[] ks, BigInteger x)
        {
            return new LongArray(x).ModInverse(m, ks).ToBigInteger();
        }

        /**
         * The auxiliary values <code>s<sub>0</sub></code> and
         * <code>s<sub>1</sub></code> used for partial modular reduction for
         * Koblitz curves.
         */
        private BigInteger[] si = null;

        private static IFiniteField BuildField(int m, int k1, int k2, int k3)
        {
            if (k1 == 0)
            {
                throw new ArgumentException("k1 must be > 0");
            }

            if (k2 == 0)
            {
                if (k3 != 0)
                {
                    throw new ArgumentException("k3 must be 0 if k2 == 0");
                }

                return FiniteFields.GetBinaryExtensionField(new int[]{ 0, k1, m });
            }

            if (k2 <= k1)
            {
                throw new ArgumentException("k2 must be > k1");
            }

            if (k3 <= k2)
            {
                throw new ArgumentException("k3 must be > k2");
            }

            return FiniteFields.GetBinaryExtensionField(new int[]{ 0, k1, k2, k3, m });
        }

        protected AbstractF2mCurve(int m, int k1, int k2, int k3)
            : base(BuildField(m, k1, k2, k3))
        {
        }

        public override bool IsValidFieldElement(BigInteger x)
        {
            return x != null && x.SignValue >= 0 && x.BitLength <= FieldSize;
        }

        public override ECPoint CreatePoint(BigInteger x, BigInteger y, bool withCompression)
        {
            ECFieldElement X = FromBigInteger(x), Y = FromBigInteger(y);

            switch (this.CoordinateSystem)
            {
                case COORD_LAMBDA_AFFINE:
                case COORD_LAMBDA_PROJECTIVE:
                {
                    if (X.IsZero)
                    {
                        if (!Y.Square().Equals(B))
                            throw new ArgumentException();
                    }
                    else
                    {
                        // Y becomes Lambda (X + Y/X) here
                        Y = Y.Divide(X).Add(X);
                    }
                    break;
                }
                default:
                {
                    break;
                }
            }

            return CreateRawPoint(X, Y, withCompression);
        }

        protected override ECPoint DecompressPoint(int yTilde, BigInteger X1)
        {
            ECFieldElement xp = FromBigInteger(X1), yp = null;
            if (xp.IsZero)
            {
                yp = B.Sqrt();
            }
            else
            {
                ECFieldElement beta = xp.Square().Invert().Multiply(B).Add(A).Add(xp);
                ECFieldElement z = SolveQuadraticEquation(beta);

                if (z != null)
                {
                    if (z.TestBitZero() != (yTilde == 1))
                    {
                        z = z.AddOne();
                    }

                    switch (this.CoordinateSystem)
                    {
                        case COORD_LAMBDA_AFFINE:
                        case COORD_LAMBDA_PROJECTIVE:
                        {
                            yp = z.Add(xp);
                            break;
                        }
                        default:
                        {
                            yp = z.Multiply(xp);
                            break;
                        }
                    }
                }
            }

            if (yp == null)
                throw new ArgumentException("Invalid point compression");

            return CreateRawPoint(xp, yp, true);
        }

        /**
         * Solves a quadratic equation <code>z<sup>2</sup> + z = beta</code>(X9.62
         * D.1.6) The other solution is <code>z + 1</code>.
         *
         * @param beta
         *            The value to solve the quadratic equation for.
         * @return the solution for <code>z<sup>2</sup> + z = beta</code> or
         *         <code>null</code> if no solution exists.
         */
        internal ECFieldElement SolveQuadraticEquation(ECFieldElement beta)
        {
            if (beta.IsZero)
                return beta;

            ECFieldElement gamma, z, zeroElement = FromBigInteger(BigInteger.Zero);

            int m = FieldSize;
            do
            {
                ECFieldElement t = FromBigInteger(BigInteger.Arbitrary(m));
                z = zeroElement;
                ECFieldElement w = beta;
                for (int i = 1; i < m; i++)
                {
                    ECFieldElement w2 = w.Square();
                    z = z.Square().Add(w2.Multiply(t));
                    w = w2.Add(beta);
                }
                if (!w.IsZero)
                {
                    return null;
                }
                gamma = z.Square().Add(z);
            }
            while (gamma.IsZero);

            return z;
        }

        /**
         * @return the auxiliary values <code>s<sub>0</sub></code> and
         * <code>s<sub>1</sub></code> used for partial modular reduction for
         * Koblitz curves.
         */
        internal virtual BigInteger[] GetSi()
        {
            if (si == null)
            {
                lock (this)
                {
                    if (si == null)
                    {
                        si = Tnaf.GetSi(this);
                    }
                }
            }
            return si;
        }

        /**
         * Returns true if this is a Koblitz curve (ABC curve).
         * @return true if this is a Koblitz curve (ABC curve), false otherwise
         */
        public virtual bool IsKoblitz
        {
            get
            {
                return m_order != null && m_cofactor != null && m_b.IsOne && (m_a.IsZero || m_a.IsOne);
            }
        }
    }

    /**
     * Elliptic curves over F2m. The Weierstrass equation is given by
     * <code>y<sup>2</sup> + xy = x<sup>3</sup> + ax<sup>2</sup> + b</code>.
     */
    internal class F2mCurve
        : AbstractF2mCurve
    {
        private const int F2M_DEFAULT_COORDS = COORD_LAMBDA_PROJECTIVE;

        /**
         * The exponent <code>m</code> of <code>F<sub>2<sup>m</sup></sub></code>.
         */
        private readonly int m;

        /**
         * TPB: The integer <code>k</code> where <code>x<sup>m</sup> +
         * x<sup>k</sup> + 1</code> represents the reduction polynomial
         * <code>f(z)</code>.<br/>
         * PPB: The integer <code>k1</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.<br/>
         */
        private readonly int k1;

        /**
         * TPB: Always set to <code>0</code><br/>
         * PPB: The integer <code>k2</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.<br/>
         */
        private readonly int k2;

        /**
         * TPB: Always set to <code>0</code><br/>
         * PPB: The integer <code>k3</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.<br/>
         */
        private readonly int k3;

        /**
         * The point at infinity on this curve.
         */
        protected readonly F2mPoint m_infinity;

        /**
         * Constructor for Trinomial Polynomial Basis (TPB).
         * @param m  The exponent <code>m</code> of
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param k The integer <code>k</code> where <code>x<sup>m</sup> +
         * x<sup>k</sup> + 1</code> represents the reduction
         * polynomial <code>f(z)</code>.
         * @param a The coefficient <code>a</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param b The coefficient <code>b</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         */
        [Obsolete("Use constructor taking order/cofactor")]
        public F2mCurve(
            int			m,
            int			k,
            BigInteger	a,
            BigInteger	b)
            : this(m, k, 0, 0, a, b, null, null)
        {
        }

        /**
         * Constructor for Trinomial Polynomial Basis (TPB).
         * @param m  The exponent <code>m</code> of
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param k The integer <code>k</code> where <code>x<sup>m</sup> +
         * x<sup>k</sup> + 1</code> represents the reduction
         * polynomial <code>f(z)</code>.
         * @param a The coefficient <code>a</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param b The coefficient <code>b</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param order The order of the main subgroup of the elliptic curve.
         * @param cofactor The cofactor of the elliptic curve, i.e.
         * <code>#E<sub>a</sub>(F<sub>2<sup>m</sup></sub>) = h * n</code>.
         */
        public F2mCurve(
            int			m, 
            int			k, 
            BigInteger	a, 
            BigInteger	b,
            BigInteger	order,
            BigInteger	cofactor)
            : this(m, k, 0, 0, a, b, order, cofactor)
        {
        }

        /**
         * Constructor for Pentanomial Polynomial Basis (PPB).
         * @param m  The exponent <code>m</code> of
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param k1 The integer <code>k1</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.
         * @param k2 The integer <code>k2</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.
         * @param k3 The integer <code>k3</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.
         * @param a The coefficient <code>a</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param b The coefficient <code>b</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         */
        [Obsolete("Use constructor taking order/cofactor")]
        public F2mCurve(
            int			m,
            int			k1,
            int			k2,
            int			k3,
            BigInteger	a,
            BigInteger	b)
            : this(m, k1, k2, k3, a, b, null, null)
        {
        }

        /**
         * Constructor for Pentanomial Polynomial Basis (PPB).
         * @param m  The exponent <code>m</code> of
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param k1 The integer <code>k1</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.
         * @param k2 The integer <code>k2</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.
         * @param k3 The integer <code>k3</code> where <code>x<sup>m</sup> +
         * x<sup>k3</sup> + x<sup>k2</sup> + x<sup>k1</sup> + 1</code>
         * represents the reduction polynomial <code>f(z)</code>.
         * @param a The coefficient <code>a</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param b The coefficient <code>b</code> in the Weierstrass equation
         * for non-supersingular elliptic curves over
         * <code>F<sub>2<sup>m</sup></sub></code>.
         * @param order The order of the main subgroup of the elliptic curve.
         * @param cofactor The cofactor of the elliptic curve, i.e.
         * <code>#E<sub>a</sub>(F<sub>2<sup>m</sup></sub>) = h * n</code>.
         */
        public F2mCurve(
            int			m, 
            int			k1, 
            int			k2, 
            int			k3,
            BigInteger	a, 
            BigInteger	b,
            BigInteger	order,
            BigInteger	cofactor)
            : base(m, k1, k2, k3)
        {
            this.m = m;
            this.k1 = k1;
            this.k2 = k2;
            this.k3 = k3;
            this.m_order = order;
            this.m_cofactor = cofactor;
            this.m_infinity = new F2mPoint(this, null, null, false);

            if (k1 == 0)
                throw new ArgumentException("k1 must be > 0");

            if (k2 == 0)
            {
                if (k3 != 0)
                    throw new ArgumentException("k3 must be 0 if k2 == 0");
            }
            else
            {
                if (k2 <= k1)
                    throw new ArgumentException("k2 must be > k1");

                if (k3 <= k2)
                    throw new ArgumentException("k3 must be > k2");
            }

            this.m_a = FromBigInteger(a);
            this.m_b = FromBigInteger(b);
            this.m_coord = F2M_DEFAULT_COORDS;
        }

        protected F2mCurve(int m, int k1, int k2, int k3, ECFieldElement a, ECFieldElement b, BigInteger order, BigInteger cofactor)
            : base(m, k1, k2, k3)
        {
            this.m = m;
            this.k1 = k1;
            this.k2 = k2;
            this.k3 = k3;
            this.m_order = order;
            this.m_cofactor = cofactor;

            this.m_infinity = new F2mPoint(this, null, null, false);
            this.m_a = a;
            this.m_b = b;
            this.m_coord = F2M_DEFAULT_COORDS;
        }

        protected override ECCurve CloneCurve()
        {
            return new F2mCurve(m, k1, k2, k3, m_a, m_b, m_order, m_cofactor);
        }

        public override bool SupportsCoordinateSystem(int coord)
        {
            switch (coord)
            {
                case COORD_AFFINE:
                case COORD_HOMOGENEOUS:
                case COORD_LAMBDA_PROJECTIVE:
                    return true;
                default:
                    return false;
            }
        }

        protected override ECMultiplier CreateDefaultMultiplier()
        {
            if (IsKoblitz)
            {
                return new WTauNafMultiplier();
            }

            return base.CreateDefaultMultiplier();
        }

        public override int FieldSize
        {
            get { return m; }
        }

        public override ECFieldElement FromBigInteger(BigInteger x)
        {
            return new F2mFieldElement(this.m, this.k1, this.k2, this.k3, x);
        }

        protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, bool withCompression)
        {
            return new F2mPoint(this, x, y, withCompression);
        }

        protected internal override ECPoint CreateRawPoint(ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, bool withCompression)
        {
            return new F2mPoint(this, x, y, zs, withCompression);
        }

        public override ECPoint Infinity
        {
            get { return m_infinity; }
        }

        public int M
        {
            get { return m; }
        }

        /**
         * Return true if curve uses a Trinomial basis.
         *
         * @return true if curve Trinomial, false otherwise.
         */
        public bool IsTrinomial()
        {
            return k2 == 0 && k3 == 0;
        }

        public int K1
        {
            get { return k1; }
        }

        public int K2
        {
            get { return k2; }
        }

        public int K3
        {
            get { return k3; }
        }

        public override ECLookupTable CreateCacheSafeLookupTable(ECPoint[] points, int off, int len)
        {
            int FE_LONGS = (m + 63) / 64;

            long[] table = new long[len * FE_LONGS * 2];
            {
                int pos = 0;
                for (int i = 0; i < len; ++i)
                {
                    ECPoint p = points[off + i];
                    ((F2mFieldElement)p.RawXCoord).x.CopyTo(table, pos); pos += FE_LONGS;
                    ((F2mFieldElement)p.RawYCoord).x.CopyTo(table, pos); pos += FE_LONGS;
                }
            }

            return new DefaultF2mLookupTable(this, table, len);
        }

        private class DefaultF2mLookupTable
            : ECLookupTable
        {
            private readonly F2mCurve m_outer;
            private readonly long[] m_table;
            private readonly int m_size;

            internal DefaultF2mLookupTable(F2mCurve outer, long[] table, int size)
            {
                this.m_outer = outer;
                this.m_table = table;
                this.m_size = size;
            }

            public virtual int Size
            {
                get { return m_size; }
            }

            public virtual ECPoint Lookup(int index)
            {
                int m = m_outer.m;
                int[] ks = m_outer.IsTrinomial() ? new int[]{ m_outer.k1 } : new int[]{ m_outer.k1, m_outer.k2, m_outer.k3 }; 

                int FE_LONGS = (m_outer.m + 63) / 64;
                long[] x = new long[FE_LONGS], y = new long[FE_LONGS];
                int pos = 0;

                for (int i = 0; i < m_size; ++i)
                {
                    long MASK =((i ^ index) - 1) >> 31;

                    for (int j = 0; j < FE_LONGS; ++j)
                    {
                        x[j] ^= m_table[pos + j] & MASK;
                        y[j] ^= m_table[pos + FE_LONGS + j] & MASK;
                    }

                    pos += (FE_LONGS * 2);
                }

                ECFieldElement X = new F2mFieldElement(m, ks, new LongArray(x));
                ECFieldElement Y = new F2mFieldElement(m, ks, new LongArray(y));
                return m_outer.CreateRawPoint(X, Y, false);
            }
        }
    }
}
