All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.open.jwtdependency.org.bouncycastle.math.ec.ECPoint Maven / Gradle / Ivy

Go to download

This is a drop in replacement for the auth0 java-jwt library (see https://github.com/auth0/java-jwt). This jar makes sure there are no external dependencies (e.g. fasterXml, Apacha Commons) needed. This is useful when deploying to an application server (e.g. tomcat with Alfreso or Pega).

The newest version!
package org.bouncycastle.math.ec;

import java.math.BigInteger;
import java.util.Hashtable;

/**
 * base class for points on elliptic curves.
 */
public abstract class ECPoint
{
    protected static ECFieldElement[] EMPTY_ZS = new ECFieldElement[0];

    protected static ECFieldElement[] getInitialZCoords(ECCurve curve)
    {
        // Cope with null curve, most commonly used by implicitlyCa
        int coord = null == curve ? ECCurve.COORD_AFFINE : curve.getCoordinateSystem();

        switch (coord)
        {
        case ECCurve.COORD_AFFINE:
        case ECCurve.COORD_LAMBDA_AFFINE:
            return EMPTY_ZS;
        default:
            break;
        }

        ECFieldElement one = curve.fromBigInteger(ECConstants.ONE);

        switch (coord)
        {
        case ECCurve.COORD_HOMOGENEOUS:
        case ECCurve.COORD_JACOBIAN:
        case ECCurve.COORD_LAMBDA_PROJECTIVE:
            return new ECFieldElement[]{ one };
        case ECCurve.COORD_JACOBIAN_CHUDNOVSKY:
            return new ECFieldElement[]{ one, one, one };
        case ECCurve.COORD_JACOBIAN_MODIFIED:
            return new ECFieldElement[]{ one, curve.getA() };
        default:
            throw new IllegalArgumentException("unknown coordinate system");
        }
    }

    protected ECCurve curve;
    protected ECFieldElement x;
    protected ECFieldElement y;
    protected ECFieldElement[] zs;

    protected boolean withCompression;

    // Hashtable is (String -> PreCompInfo)
    protected Hashtable preCompTable = null;

    protected ECPoint(ECCurve curve, ECFieldElement x, ECFieldElement y)
    {
        this(curve, x, y, getInitialZCoords(curve));
    }

    protected ECPoint(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs)
    {
        this.curve = curve;
        this.x = x;
        this.y = y;
        this.zs = zs;
    }

    protected boolean satisfiesCofactor()
    {
        BigInteger h = curve.getCofactor();
        return h == null || h.equals(ECConstants.ONE) || !ECAlgorithms.referenceMultiply(this, h).isInfinity();
    }

    protected abstract boolean satisfiesCurveEquation();

    public final ECPoint getDetachedPoint()
    {
        return normalize().detach();
    }

    public ECCurve getCurve()
    {
        return curve;
    }

    protected abstract ECPoint detach();

    protected int getCurveCoordinateSystem()
    {
        // Cope with null curve, most commonly used by implicitlyCa
        return null == curve ? ECCurve.COORD_AFFINE : curve.getCoordinateSystem();
    }

    /**
     * Normalizes this point, and then returns the affine x-coordinate.
     * 
     * Note: normalization can be expensive, this method is deprecated in favour
     * of caller-controlled normalization.
     * 
     * @deprecated Use getAffineXCoord(), or normalize() and getXCoord(), instead
     */
    public ECFieldElement getX()
    {
        return normalize().getXCoord();
    }


    /**
     * Normalizes this point, and then returns the affine y-coordinate.
     * 
     * Note: normalization can be expensive, this method is deprecated in favour
     * of caller-controlled normalization.
     * 
     * @deprecated Use getAffineYCoord(), or normalize() and getYCoord(), instead
     */
    public ECFieldElement getY()
    {
        return normalize().getYCoord();
    }

    /**
     * Returns the affine x-coordinate after checking that this point is normalized.
     * 
     * @return The affine x-coordinate of this point
     * @throws IllegalStateException if the point is not normalized
     */
    public ECFieldElement getAffineXCoord()
    {
        checkNormalized();
        return getXCoord();
    }

    /**
     * Returns the affine y-coordinate after checking that this point is normalized
     * 
     * @return The affine y-coordinate of this point
     * @throws IllegalStateException if the point is not normalized
     */
    public ECFieldElement getAffineYCoord()
    {
        checkNormalized();
        return getYCoord();
    }

    /**
     * Returns the x-coordinate.
     * 
     * Caution: depending on the curve's coordinate system, this may not be the same value as in an
     * affine coordinate system; use normalize() to get a point where the coordinates have their
     * affine values, or use getAffineXCoord() if you expect the point to already have been
     * normalized.
     * 
     * @return the x-coordinate of this point
     */
    public ECFieldElement getXCoord()
    {
        return x;
    }

    /**
     * Returns the y-coordinate.
     * 
     * Caution: depending on the curve's coordinate system, this may not be the same value as in an
     * affine coordinate system; use normalize() to get a point where the coordinates have their
     * affine values, or use getAffineYCoord() if you expect the point to already have been
     * normalized.
     * 
     * @return the y-coordinate of this point
     */
    public ECFieldElement getYCoord()
    {
        return y;
    }

    public ECFieldElement getZCoord(int index)
    {
        return (index < 0 || index >= zs.length) ? null : zs[index];
    }

    public ECFieldElement[] getZCoords()
    {
        int zsLen = zs.length;
        if (zsLen == 0)
        {
            return EMPTY_ZS;
        }
        ECFieldElement[] copy = new ECFieldElement[zsLen];
        System.arraycopy(zs, 0, copy, 0, zsLen);
        return copy;
    }

    public final ECFieldElement getRawXCoord()
    {
        return x;
    }

    public final ECFieldElement getRawYCoord()
    {
        return y;
    }

    protected final ECFieldElement[] getRawZCoords()
    {
        return zs;
    }

    protected void checkNormalized()
    {
        if (!isNormalized())
        {
            throw new IllegalStateException("point not in normal form");
        }
    }

    public boolean isNormalized()
    {
        int coord = this.getCurveCoordinateSystem();

        return coord == ECCurve.COORD_AFFINE
            || coord == ECCurve.COORD_LAMBDA_AFFINE
            || isInfinity()
            || zs[0].isOne();
    }

    /**
     * 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.
     * 
     * @return a new ECPoint instance representing the same point, but with normalized coordinates
     */
    public ECPoint normalize()
    {
        if (this.isInfinity())
        {
            return this;
        }

        switch (this.getCurveCoordinateSystem())
        {
        case ECCurve.COORD_AFFINE:
        case ECCurve.COORD_LAMBDA_AFFINE:
        {
            return this;
        }
        default:
        {
            ECFieldElement Z1 = getZCoord(0);
            if (Z1.isOne())
            {
                return this;
            }

            return normalize(Z1.invert());
        }
        }
    }

    ECPoint normalize(ECFieldElement zInv)
    {
        switch (this.getCurveCoordinateSystem())
        {
        case ECCurve.COORD_HOMOGENEOUS:
        case ECCurve.COORD_LAMBDA_PROJECTIVE:
        {
            return createScaledPoint(zInv, zInv);
        }
        case ECCurve.COORD_JACOBIAN:
        case ECCurve.COORD_JACOBIAN_CHUDNOVSKY:
        case ECCurve.COORD_JACOBIAN_MODIFIED:
        {
            ECFieldElement zInv2 = zInv.square(), zInv3 = zInv2.multiply(zInv);
            return createScaledPoint(zInv2, zInv3);
        }
        default:
        {
            throw new IllegalStateException("not a projective coordinate system");
        }
        }
    }

    protected ECPoint createScaledPoint(ECFieldElement sx, ECFieldElement sy)
    {
        return this.getCurve().createRawPoint(getRawXCoord().multiply(sx), getRawYCoord().multiply(sy), this.withCompression);
    }

    public boolean isInfinity()
    {
        return x == null || y == null || (zs.length > 0 && zs[0].isZero());
    }

    /**
     * @deprecated per-point compression property will be removed, refer {@link #getEncoded(boolean)}
     */
    public boolean isCompressed()
    {
        return this.withCompression;
    }

    public boolean isValid()
    {
        if (isInfinity())
        {
            return true;
        }

        // TODO Sanity-check the field elements

        ECCurve curve = getCurve();
        if (curve != null)
        {
            if (!satisfiesCurveEquation())
            {
                return false;
            }

            if (!satisfiesCofactor())
            {
                return false;
            }
        }

        return true;
    }

    public ECPoint scaleX(ECFieldElement scale)
    {
        return isInfinity()
            ?   this
            :   getCurve().createRawPoint(getRawXCoord().multiply(scale), getRawYCoord(), getRawZCoords(), this.withCompression);
    }

    public ECPoint scaleY(ECFieldElement scale)
    {
        return isInfinity()
            ?   this
            :   getCurve().createRawPoint(getRawXCoord(), getRawYCoord().multiply(scale), getRawZCoords(), this.withCompression);
    }

    public boolean equals(ECPoint other)
    {
        if (null == other)
        {
            return false;
        }

        ECCurve c1 = this.getCurve(), c2 = other.getCurve();
        boolean n1 = (null == c1), n2 = (null == c2);
        boolean i1 = isInfinity(), i2 = other.isInfinity();

        if (i1 || i2)
        {
            return (i1 && i2) && (n1 || n2 || c1.equals(c2));
        }

        ECPoint p1 = this, p2 = other;
        if (n1 && n2)
        {
            // Points with null curve are in affine form, so already normalized
        }
        else if (n1)
        {
            p2 = p2.normalize();
        }
        else if (n2)
        {
            p1 = p1.normalize();
        }
        else if (!c1.equals(c2))
        {
            return false;
        }
        else
        {
            // TODO Consider just requiring already normalized, to avoid silent performance degradation

            ECPoint[] points = new ECPoint[]{ this, c1.importPoint(p2) };

            // TODO This is a little strong, really only requires coZNormalizeAll to get Zs equal
            c1.normalizeAll(points);

            p1 = points[0];
            p2 = points[1];
        }

        return p1.getXCoord().equals(p2.getXCoord()) && p1.getYCoord().equals(p2.getYCoord());
    }

    public boolean equals(Object other)
    {
        if (other == this)
        {
            return true;
        }

        if (!(other instanceof ECPoint))
        {
            return false;
        }

        return equals((ECPoint)other);
    }

    public int hashCode()
    {
        ECCurve c = this.getCurve();
        int hc = (null == c) ? 0 : ~c.hashCode();

        if (!this.isInfinity())
        {
            // TODO Consider just requiring already normalized, to avoid silent performance degradation

            ECPoint p = normalize();

            hc ^= p.getXCoord().hashCode() * 17;
            hc ^= p.getYCoord().hashCode() * 257;
        }

        return hc;
    }

    public String toString()
    {
        if (this.isInfinity())
        {
            return "INF";
        }

        StringBuffer sb = new StringBuffer();
        sb.append('(');
        sb.append(getRawXCoord());
        sb.append(',');
        sb.append(getRawYCoord());
        for (int i = 0; i < zs.length; ++i)
        {
            sb.append(',');
            sb.append(zs[i]);
        }
        sb.append(')');
        return sb.toString();
    }

    /**
     * @deprecated per-point compression property will be removed, refer {@link #getEncoded(boolean)}
     */
    public byte[] getEncoded()
    {
        return getEncoded(this.withCompression);
    }

    /**
     * Get an encoding of the point value, optionally in compressed format.
     * 
     * @param compressed whether to generate a compressed point encoding.
     * @return the point encoding
     */
    public byte[] getEncoded(boolean compressed)
    {
        if (this.isInfinity())
        {
            return new byte[1];
        }

        ECPoint normed = normalize();

        byte[] X = normed.getXCoord().getEncoded();

        if (compressed)
        {
            byte[] PO = new byte[X.length + 1];
            PO[0] = (byte)(normed.getCompressionYTilde() ? 0x03 : 0x02);
            System.arraycopy(X, 0, PO, 1, X.length);
            return PO;
        }

        byte[] Y = normed.getYCoord().getEncoded();

        byte[] PO = new byte[X.length + Y.length + 1];
        PO[0] = 0x04;
        System.arraycopy(X, 0, PO, 1, X.length);
        System.arraycopy(Y, 0, PO, X.length + 1, Y.length);
        return PO;
    }

    protected abstract boolean getCompressionYTilde();

    public abstract ECPoint add(ECPoint b);

    public abstract ECPoint negate();

    public abstract ECPoint subtract(ECPoint b);

    public ECPoint timesPow2(int e)
    {
        if (e < 0)
        {
            throw new IllegalArgumentException("'e' cannot be negative");
        }

        ECPoint p = this;
        while (--e >= 0)
        {
            p = p.twice();
        }
        return p;
    }

    public abstract ECPoint twice();

    public ECPoint twicePlus(ECPoint b)
    {
        return twice().add(b);
    }

    public ECPoint threeTimes()
    {
        return twicePlus(this);
    }

    /**
     * Multiplies this ECPoint by the given number.
     * @param k The multiplicator.
     * @return k * this.
     */
    public ECPoint multiply(BigInteger k)
    {
        return this.getCurve().getMultiplier().multiply(this, k);
    }

    public static abstract class AbstractFp extends ECPoint
    {
        protected AbstractFp(ECCurve curve, ECFieldElement x, ECFieldElement y)
        {
            super(curve, x, y);
        }

        protected AbstractFp(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs)
        {
            super(curve, x, y, zs);
        }

        protected boolean getCompressionYTilde()
        {
            return this.getAffineYCoord().testBitZero();
        }

        protected boolean satisfiesCurveEquation()
        {
            ECFieldElement X = this.x, Y = this.y, A = curve.getA(), B = curve.getB();
            ECFieldElement lhs = Y.square();

            switch (this.getCurveCoordinateSystem())
            {
            case ECCurve.COORD_AFFINE:
                break;
            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Z = this.zs[0];
                if (!Z.isOne())
                {
                    ECFieldElement Z2 = Z.square(), Z3 = Z.multiply(Z2);
                    lhs = lhs.multiply(Z);
                    A = A.multiply(Z2);
                    B = B.multiply(Z3);
                }
                break;
            }
            case ECCurve.COORD_JACOBIAN:
            case ECCurve.COORD_JACOBIAN_CHUDNOVSKY:
            case ECCurve.COORD_JACOBIAN_MODIFIED:
            {
                ECFieldElement Z = this.zs[0];
                if (!Z.isOne())
                {
                    ECFieldElement Z2 = Z.square(), Z4 = Z2.square(), Z6 = Z2.multiply(Z4);
                    A = A.multiply(Z4);
                    B = B.multiply(Z6);
                }
                break;
            }
            default:
                throw new IllegalStateException("unsupported coordinate system");
            }

            ECFieldElement rhs = X.square().add(A).multiply(X).add(B);
            return lhs.equals(rhs);
        }

        public ECPoint subtract(ECPoint b)
        {
            if (b.isInfinity())
            {
                return this;
            }

            // Add -b
            return this.add(b.negate());
        }
    }

    /**
     * Elliptic curve points over Fp
     */
    public static class Fp extends AbstractFp
    {
        /**
         * Create a point which encodes without point compression.
         * 
         * @param curve the curve to use
         * @param x affine x co-ordinate
         * @param y affine y co-ordinate
         * 
         * @deprecated Use ECCurve.createPoint to construct points
         */
        public Fp(ECCurve curve, ECFieldElement x, ECFieldElement y)
        {
            this(curve, x, y, false);
        }

        /**
         * Create a point that encodes with or without point compression.
         * 
         * @param curve the curve to use
         * @param x affine x co-ordinate
         * @param y affine y co-ordinate
         * @param withCompression if true encode with point compression
         * 
         * @deprecated per-point compression property will be removed, refer {@link #getEncoded(boolean)}
         */
        public Fp(ECCurve curve, ECFieldElement x, ECFieldElement y, boolean withCompression)
        {
            super(curve, x, y);

            if ((x == null) != (y == null))
            {
                throw new IllegalArgumentException("Exactly one of the field elements is null");
            }

            this.withCompression = withCompression;
        }

        Fp(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, boolean withCompression)
        {
            super(curve, x, y, zs);

            this.withCompression = withCompression;
        }

        protected ECPoint detach()
        {
            return new ECPoint.Fp(null, this.getAffineXCoord(), this.getAffineYCoord());
        }

        public ECFieldElement getZCoord(int index)
        {
            if (index == 1 && ECCurve.COORD_JACOBIAN_MODIFIED == this.getCurveCoordinateSystem())
            {
                return getJacobianModifiedW();
            }

            return super.getZCoord(index);
        }

        // B.3 pg 62
        public ECPoint add(ECPoint b)
        {
            if (this.isInfinity())
            {
                return b;
            }
            if (b.isInfinity())
            {
                return this;
            }
            if (this == b)
            {
                return twice();
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            ECFieldElement X1 = this.x, Y1 = this.y;
            ECFieldElement X2 = b.x, Y2 = b.y;

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement dx = X2.subtract(X1), dy = Y2.subtract(Y1);

                if (dx.isZero())
                {
                    if (dy.isZero())
                    {
                        // this == b, i.e. this must be doubled
                        return twice();
                    }

                    // this == -b, i.e. the result is the point at infinity
                    return curve.getInfinity();
                }

                ECFieldElement gamma = dy.divide(dx);
                ECFieldElement X3 = gamma.square().subtract(X1).subtract(X2);
                ECFieldElement Y3 = gamma.multiply(X1.subtract(X3)).subtract(Y1);

                return new ECPoint.Fp(curve, X3, Y3, this.withCompression);
            }

            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Z1 = this.zs[0];
                ECFieldElement Z2 = b.zs[0];

                boolean Z1IsOne = Z1.isOne();
                boolean Z2IsOne = Z2.isOne();

                ECFieldElement u1 = Z1IsOne ? Y2 : Y2.multiply(Z1);
                ECFieldElement u2 = Z2IsOne ? Y1 : Y1.multiply(Z2);
                ECFieldElement u = u1.subtract(u2);
                ECFieldElement v1 = Z1IsOne ? X2 : X2.multiply(Z1);
                ECFieldElement v2 = Z2IsOne ? X1 : X1.multiply(Z2);
                ECFieldElement v = v1.subtract(v2);

                // Check if b == this or b == -this
                if (v.isZero())
                {
                    if (u.isZero())
                    {
                        // this == b, i.e. this must be doubled
                        return this.twice();
                    }

                    // this == -b, i.e. the result is the point at infinity
                    return curve.getInfinity();
                }

                // TODO Optimize for when w == 1
                ECFieldElement w = Z1IsOne ? Z2 : Z2IsOne ? Z1 : Z1.multiply(Z2);
                ECFieldElement vSquared = v.square();
                ECFieldElement vCubed = vSquared.multiply(v);
                ECFieldElement vSquaredV2 = vSquared.multiply(v2);
                ECFieldElement A = u.square().multiply(w).subtract(vCubed).subtract(two(vSquaredV2));

                ECFieldElement X3 = v.multiply(A);
                ECFieldElement Y3 = vSquaredV2.subtract(A).multiplyMinusProduct(u, u2, vCubed);
                ECFieldElement Z3 = vCubed.multiply(w);

                return new ECPoint.Fp(curve, X3, Y3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }

            case ECCurve.COORD_JACOBIAN:
            case ECCurve.COORD_JACOBIAN_MODIFIED:
            {
                ECFieldElement Z1 = this.zs[0];
                ECFieldElement Z2 = b.zs[0];

                boolean Z1IsOne = Z1.isOne();

                ECFieldElement X3, Y3, Z3, Z3Squared = null;

                if (!Z1IsOne && Z1.equals(Z2))
                {
                    // TODO Make this available as public method coZAdd?

                    ECFieldElement dx = X1.subtract(X2), dy = Y1.subtract(Y2);
                    if (dx.isZero())
                    {
                        if (dy.isZero())
                        {
                            return twice();
                        }
                        return curve.getInfinity();
                    }

                    ECFieldElement C = dx.square();
                    ECFieldElement W1 = X1.multiply(C), W2 = X2.multiply(C);
                    ECFieldElement A1 = W1.subtract(W2).multiply(Y1);

                    X3 = dy.square().subtract(W1).subtract(W2);
                    Y3 = W1.subtract(X3).multiply(dy).subtract(A1);
                    Z3 = dx;

                    Z3 = Z3.multiply(Z1);
                }
                else
                {
                    ECFieldElement Z1Squared, U2, S2;
                    if (Z1IsOne)
                    {
                        Z1Squared = Z1; U2 = X2; S2 = Y2;
                    }
                    else
                    {
                        Z1Squared = Z1.square();
                        U2 = Z1Squared.multiply(X2);
                        ECFieldElement Z1Cubed = Z1Squared.multiply(Z1);
                        S2 = Z1Cubed.multiply(Y2);
                    }

                    boolean Z2IsOne = Z2.isOne();
                    ECFieldElement Z2Squared, U1, S1;
                    if (Z2IsOne)
                    {
                        Z2Squared = Z2; U1 = X1; S1 = Y1;
                    }
                    else
                    {
                        Z2Squared = Z2.square();
                        U1 = Z2Squared.multiply(X1); 
                        ECFieldElement Z2Cubed = Z2Squared.multiply(Z2);
                        S1 = Z2Cubed.multiply(Y1);
                    }

                    ECFieldElement H = U1.subtract(U2);
                    ECFieldElement R = S1.subtract(S2);
    
                    // Check if b == this or b == -this
                    if (H.isZero())
                    {
                        if (R.isZero())
                        {
                            // this == b, i.e. this must be doubled
                            return this.twice();
                        }
    
                        // this == -b, i.e. the result is the point at infinity
                        return curve.getInfinity();
                    }
    
                    ECFieldElement HSquared = H.square();
                    ECFieldElement G = HSquared.multiply(H);
                    ECFieldElement V = HSquared.multiply(U1);
    
                    X3 = R.square().add(G).subtract(two(V));
                    Y3 = V.subtract(X3).multiplyMinusProduct(R, G, S1);

                    Z3 = H;
                    if (!Z1IsOne)
                    {
                        Z3 = Z3.multiply(Z1);
                    }
                    if (!Z2IsOne)
                    {
                        Z3 = Z3.multiply(Z2);
                    }
    
                    // Alternative calculation of Z3 using fast square
    //                X3 = four(X3);
    //                Y3 = eight(Y3);
    //                Z3 = doubleProductFromSquares(Z1, Z2, Z1Squared, Z2Squared).multiply(H);
                    
                    if (Z3 == H)
                    {
                        Z3Squared = HSquared;
                    }
                }

                ECFieldElement[] zs;
                if (coord == ECCurve.COORD_JACOBIAN_MODIFIED)
                {
                    // TODO If the result will only be used in a subsequent addition, we don't need W3
                    ECFieldElement W3 = calculateJacobianModifiedW(Z3, Z3Squared);

                    zs = new ECFieldElement[]{ Z3, W3 };
                }
                else
                {
                    zs = new ECFieldElement[]{ Z3 };
                }

                return new ECPoint.Fp(curve, X3, Y3, zs, this.withCompression);
            }

            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }

        // B.3 pg 62
        public ECPoint twice()
        {
            if (this.isInfinity())
            {
                return this;
            }

            ECCurve curve = this.getCurve();

            ECFieldElement Y1 = this.y;
            if (Y1.isZero()) 
            {
                return curve.getInfinity();
            }

            int coord = curve.getCoordinateSystem();

            ECFieldElement X1 = this.x;

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement X1Squared = X1.square();
                ECFieldElement gamma = three(X1Squared).add(this.getCurve().getA()).divide(two(Y1));
                ECFieldElement X3 = gamma.square().subtract(two(X1));
                ECFieldElement Y3 = gamma.multiply(X1.subtract(X3)).subtract(Y1);
    
                return new ECPoint.Fp(curve, X3, Y3, this.withCompression);
            }

            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Z1 = this.zs[0];

                boolean Z1IsOne = Z1.isOne();

                // TODO Optimize for small negative a4 and -3
                ECFieldElement w = curve.getA();
                if (!w.isZero() && !Z1IsOne)
                {
                    w = w.multiply(Z1.square());
                }
                w = w.add(three(X1.square()));
                
                ECFieldElement s = Z1IsOne ? Y1 : Y1.multiply(Z1);
                ECFieldElement t = Z1IsOne ? Y1.square() : s.multiply(Y1);
                ECFieldElement B = X1.multiply(t);
                ECFieldElement _4B = four(B);
                ECFieldElement h = w.square().subtract(two(_4B));

                ECFieldElement _2s = two(s);
                ECFieldElement X3 = h.multiply(_2s);
                ECFieldElement _2t = two(t);
                ECFieldElement Y3 = _4B.subtract(h).multiply(w).subtract(two(_2t.square()));
                ECFieldElement _4sSquared = Z1IsOne ? two(_2t) : _2s.square();
                ECFieldElement Z3 = two(_4sSquared).multiply(s);

                return new ECPoint.Fp(curve, X3, Y3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }

            case ECCurve.COORD_JACOBIAN:
            {
                ECFieldElement Z1 = this.zs[0];

                boolean Z1IsOne = Z1.isOne();

                ECFieldElement Y1Squared = Y1.square();
                ECFieldElement T = Y1Squared.square();

                ECFieldElement a4 = curve.getA();
                ECFieldElement a4Neg = a4.negate();

                ECFieldElement M, S;
                if (a4Neg.toBigInteger().equals(BigInteger.valueOf(3)))
                {
                    ECFieldElement Z1Squared = Z1IsOne ? Z1 : Z1.square();
                    M = three(X1.add(Z1Squared).multiply(X1.subtract(Z1Squared)));
                    S = four(Y1Squared.multiply(X1));
                }
                else
                {
                    ECFieldElement X1Squared = X1.square();
                    M = three(X1Squared);
                    if (Z1IsOne)
                    {
                        M = M.add(a4);
                    }
                    else if (!a4.isZero())
                    {
                        ECFieldElement Z1Squared = Z1.square();
                        ECFieldElement Z1Pow4 = Z1Squared.square();
                        if (a4Neg.bitLength() < a4.bitLength())
                        {
                            M = M.subtract(Z1Pow4.multiply(a4Neg));
                        }
                        else
                        {
                            M = M.add(Z1Pow4.multiply(a4));
                        }
                    }
//                  S = two(doubleProductFromSquares(X1, Y1Squared, X1Squared, T));
                    S = four(X1.multiply(Y1Squared));
                }

                ECFieldElement X3 = M.square().subtract(two(S));
                ECFieldElement Y3 = S.subtract(X3).multiply(M).subtract(eight(T));

                ECFieldElement Z3 = two(Y1);
                if (!Z1IsOne)
                {
                    Z3 = Z3.multiply(Z1);
                }

                // Alternative calculation of Z3 using fast square
//                ECFieldElement Z3 = doubleProductFromSquares(Y1, Z1, Y1Squared, Z1Squared);

                return new ECPoint.Fp(curve, X3, Y3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }

            case ECCurve.COORD_JACOBIAN_MODIFIED:
            {
                return twiceJacobianModified(true);
            }

            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }

        public ECPoint twicePlus(ECPoint b)
        {
            if (this == b)
            {
                return threeTimes();
            }
            if (this.isInfinity())
            {
                return b;
            }
            if (b.isInfinity())
            {
                return twice();
            }

            ECFieldElement Y1 = this.y;
            if (Y1.isZero()) 
            {
                return b;
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement X1 = this.x;
                ECFieldElement X2 = b.x, Y2 = b.y;

                ECFieldElement dx = X2.subtract(X1), dy = Y2.subtract(Y1);

                if (dx.isZero())
                {
                    if (dy.isZero())
                    {
                        // this == b i.e. the result is 3P
                        return threeTimes();
                    }

                    // this == -b, i.e. the result is P
                    return this;
                }

                /*
                 * Optimized calculation of 2P + Q, as described in "Trading Inversions for
                 * Multiplications in Elliptic Curve Cryptography", by Ciet, Joye, Lauter, Montgomery.
                 */

                ECFieldElement X = dx.square(), Y = dy.square();
                ECFieldElement d = X.multiply(two(X1).add(X2)).subtract(Y);
                if (d.isZero())
                {
                    return curve.getInfinity();
                }

                ECFieldElement D = d.multiply(dx);
                ECFieldElement I = D.invert();
                ECFieldElement L1 = d.multiply(I).multiply(dy);
                ECFieldElement L2 = two(Y1).multiply(X).multiply(dx).multiply(I).subtract(L1);
                ECFieldElement X4 = (L2.subtract(L1)).multiply(L1.add(L2)).add(X2);
                ECFieldElement Y4 = (X1.subtract(X4)).multiply(L2).subtract(Y1);

                return new ECPoint.Fp(curve, X4, Y4, this.withCompression);
            }
            case ECCurve.COORD_JACOBIAN_MODIFIED:
            {
                return twiceJacobianModified(false).add(b);
            }
            default:
            {
                return twice().add(b);
            }
            }
        }

        public ECPoint threeTimes()
        {
            if (this.isInfinity())
            {
                return this;
            }

            ECFieldElement Y1 = this.y;
            if (Y1.isZero())
            {
                return this;
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement X1 = this.x;

                ECFieldElement _2Y1 = two(Y1); 
                ECFieldElement X = _2Y1.square();
                ECFieldElement Z = three(X1.square()).add(this.getCurve().getA());
                ECFieldElement Y = Z.square();

                ECFieldElement d = three(X1).multiply(X).subtract(Y);
                if (d.isZero())
                {
                    return this.getCurve().getInfinity();
                }

                ECFieldElement D = d.multiply(_2Y1); 
                ECFieldElement I = D.invert();
                ECFieldElement L1 = d.multiply(I).multiply(Z);
                ECFieldElement L2 = X.square().multiply(I).subtract(L1);

                ECFieldElement X4 = (L2.subtract(L1)).multiply(L1.add(L2)).add(X1);
                ECFieldElement Y4 = (X1.subtract(X4)).multiply(L2).subtract(Y1); 
                return new ECPoint.Fp(curve, X4, Y4, this.withCompression);
            }
            case ECCurve.COORD_JACOBIAN_MODIFIED:
            {
                return twiceJacobianModified(false).add(this);
            }
            default:
            {
                // NOTE: Be careful about recursions between twicePlus and threeTimes
                return twice().add(this);
            }
            }
        }

        public ECPoint timesPow2(int e)
        {
            if (e < 0)
            {
                throw new IllegalArgumentException("'e' cannot be negative");
            }
            if (e == 0 || this.isInfinity())
            {
                return this;
            }
            if (e == 1)
            {
                return twice();
            }

            ECCurve curve = this.getCurve();

            ECFieldElement Y1 = this.y;
            if (Y1.isZero()) 
            {
                return curve.getInfinity();
            }

            int coord = curve.getCoordinateSystem();

            ECFieldElement W1 = curve.getA();
            ECFieldElement X1 = this.x;
            ECFieldElement Z1 = this.zs.length < 1 ? curve.fromBigInteger(ECConstants.ONE) : this.zs[0];

            if (!Z1.isOne())
            {
                switch (coord)
                {
                case ECCurve.COORD_AFFINE:
                    break;
                case ECCurve.COORD_HOMOGENEOUS:
                    ECFieldElement Z1Sq = Z1.square();
                    X1 = X1.multiply(Z1);
                    Y1 = Y1.multiply(Z1Sq);
                    W1 = calculateJacobianModifiedW(Z1, Z1Sq);
                    break;
                case ECCurve.COORD_JACOBIAN:
                    W1 = calculateJacobianModifiedW(Z1, null);
                    break;
                case ECCurve.COORD_JACOBIAN_MODIFIED:
                    W1 = getJacobianModifiedW();
                    break;
                default:
                    throw new IllegalStateException("unsupported coordinate system");
                }
            }

            for (int i = 0; i < e; ++i)
            {
                if (Y1.isZero()) 
                {
                    return curve.getInfinity();
                }

                ECFieldElement X1Squared = X1.square();
                ECFieldElement M = three(X1Squared);
                ECFieldElement _2Y1 = two(Y1);
                ECFieldElement _2Y1Squared = _2Y1.multiply(Y1);
                ECFieldElement S = two(X1.multiply(_2Y1Squared));
                ECFieldElement _4T = _2Y1Squared.square();
                ECFieldElement _8T = two(_4T);

                if (!W1.isZero())
                {
                    M = M.add(W1);
                    W1 = two(_8T.multiply(W1));
                }

                X1 = M.square().subtract(two(S));
                Y1 = M.multiply(S.subtract(X1)).subtract(_8T);
                Z1 = Z1.isOne() ? _2Y1 : _2Y1.multiply(Z1);
            }

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
                ECFieldElement zInv = Z1.invert(), zInv2 = zInv.square(), zInv3 = zInv2.multiply(zInv);
                return new Fp(curve, X1.multiply(zInv2), Y1.multiply(zInv3), this.withCompression);
            case ECCurve.COORD_HOMOGENEOUS:
                X1 = X1.multiply(Z1);
                Z1 = Z1.multiply(Z1.square());
                return new Fp(curve, X1, Y1, new ECFieldElement[]{ Z1 }, this.withCompression);
            case ECCurve.COORD_JACOBIAN:
                return new Fp(curve, X1, Y1, new ECFieldElement[]{ Z1 }, this.withCompression);
            case ECCurve.COORD_JACOBIAN_MODIFIED:
                return new Fp(curve, X1, Y1, new ECFieldElement[]{ Z1, W1 }, this.withCompression);
            default:
                throw new IllegalStateException("unsupported coordinate system");
            }
        }

        protected ECFieldElement two(ECFieldElement x)
        {
            return x.add(x);
        }

        protected ECFieldElement three(ECFieldElement x)
        {
            return two(x).add(x);
        }

        protected ECFieldElement four(ECFieldElement x)
        {
            return two(two(x));
        }

        protected ECFieldElement eight(ECFieldElement x)
        {
            return four(two(x));
        }

        protected ECFieldElement doubleProductFromSquares(ECFieldElement a, ECFieldElement b,
            ECFieldElement aSquared, ECFieldElement bSquared)
        {
            /*
             * NOTE: If squaring in the field is faster than multiplication, then this is a quicker
             * way to calculate 2.A.B, if A^2 and B^2 are already known.
             */
            return a.add(b).square().subtract(aSquared).subtract(bSquared);
        }

        public ECPoint negate()
        {
            if (this.isInfinity())
            {
                return this;
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            if (ECCurve.COORD_AFFINE != coord)
            {
                return new ECPoint.Fp(curve, this.x, this.y.negate(), this.zs, this.withCompression);
            }

            return new ECPoint.Fp(curve, this.x, this.y.negate(), this.withCompression);
        }

        protected ECFieldElement calculateJacobianModifiedW(ECFieldElement Z, ECFieldElement ZSquared)
        {
            ECFieldElement a4 = this.getCurve().getA();
            if (a4.isZero() || Z.isOne())
            {
                return a4;
            }

            if (ZSquared == null)
            {
                ZSquared = Z.square();
            }

            ECFieldElement W = ZSquared.square();
            ECFieldElement a4Neg = a4.negate();
            if (a4Neg.bitLength() < a4.bitLength())
            {
                W = W.multiply(a4Neg).negate();
            }
            else
            {
                W = W.multiply(a4);
            }
            return W;
        }

        protected ECFieldElement getJacobianModifiedW()
        {
            ECFieldElement W = this.zs[1];
            if (W == null)
            {
                // NOTE: Rarely, twicePlus will result in the need for a lazy W1 calculation here
                this.zs[1] = W = calculateJacobianModifiedW(this.zs[0], null);
            }
            return W;
        }

        protected ECPoint.Fp twiceJacobianModified(boolean calculateW)
        {
            ECFieldElement X1 = this.x, Y1 = this.y, Z1 = this.zs[0], W1 = getJacobianModifiedW();

            ECFieldElement X1Squared = X1.square();
            ECFieldElement M = three(X1Squared).add(W1);
            ECFieldElement _2Y1 = two(Y1);
            ECFieldElement _2Y1Squared = _2Y1.multiply(Y1);
            ECFieldElement S = two(X1.multiply(_2Y1Squared));
            ECFieldElement X3 = M.square().subtract(two(S));
            ECFieldElement _4T = _2Y1Squared.square();
            ECFieldElement _8T = two(_4T);
            ECFieldElement Y3 = M.multiply(S.subtract(X3)).subtract(_8T);
            ECFieldElement W3 = calculateW ? two(_8T.multiply(W1)) : null;
            ECFieldElement Z3 = Z1.isOne() ? _2Y1 : _2Y1.multiply(Z1);

            return new ECPoint.Fp(this.getCurve(), X3, Y3, new ECFieldElement[]{ Z3, W3 }, this.withCompression);
        }
    }

    public static abstract class AbstractF2m extends ECPoint
    {
        protected AbstractF2m(ECCurve curve, ECFieldElement x, ECFieldElement y)
        {
            super(curve, x, y);
        }

        protected AbstractF2m(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs)
        {
            super(curve, x, y, zs);
        }

        protected boolean satisfiesCurveEquation()
        {
            ECCurve curve = this.getCurve();
            ECFieldElement X = this.x, A = curve.getA(), B = curve.getB();
            
            int coord = curve.getCoordinateSystem();
            if (coord == ECCurve.COORD_LAMBDA_PROJECTIVE)
            {
                ECFieldElement Z = this.zs[0];
                boolean ZIsOne = Z.isOne();

                if (X.isZero())
                {
                    // NOTE: For x == 0, we expect the affine-y instead of the lambda-y 
                    ECFieldElement Y = this.y;
                    ECFieldElement lhs = Y.square(), rhs = B;
                    if (!ZIsOne)
                    {
                        rhs = rhs.multiply(Z.square());
                    }
                    return lhs.equals(rhs);
                }

                ECFieldElement L = this.y, X2 = X.square();
                ECFieldElement lhs, rhs;
                if (ZIsOne)
                {
                    lhs = L.square().add(L).add(A);
                    rhs = X2.square().add(B);
                }
                else
                {
                    ECFieldElement Z2 = Z.square(), Z4 = Z2.square();
                    lhs = L.add(Z).multiplyPlusProduct(L, A, Z2);
                    // TODO If sqrt(b) is precomputed this can be simplified to a single square
                    rhs = X2.squarePlusProduct(B, Z4);
                }
                lhs = lhs.multiply(X2);
                return lhs.equals(rhs);
            }

            ECFieldElement Y = this.y;
            ECFieldElement lhs = Y.add(X).multiply(Y);

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
                break;
            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Z = this.zs[0];
                if (!Z.isOne())
                {
                    ECFieldElement Z2 = Z.square(), Z3 = Z.multiply(Z2);
                    lhs = lhs.multiply(Z);
                    A = A.multiply(Z);
                    B = B.multiply(Z3);
                }
                break;
            }
            default:
                throw new IllegalStateException("unsupported coordinate system");
            }

            ECFieldElement rhs = X.add(A).multiply(X.square()).add(B);
            return lhs.equals(rhs);
        }

        public ECPoint scaleX(ECFieldElement scale)
        {
            if (this.isInfinity())
            {
                return this;
            }

            int coord = this.getCurveCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_LAMBDA_AFFINE:
            {
                // Y is actually Lambda (X + Y/X) here
                ECFieldElement X = this.getRawXCoord(), L = this.getRawYCoord(); // earlier JDK

                ECFieldElement X2 = X.multiply(scale);
                ECFieldElement L2 = L.add(X).divide(scale).add(X2);

                return this.getCurve().createRawPoint(X, L2, this.getRawZCoords(), this.withCompression); // earlier JDK
            }
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                // Y is actually Lambda (X + Y/X) here
                ECFieldElement X = this.getRawXCoord(), L = this.getRawYCoord(), Z = this.getRawZCoords()[0]; // earlier JDK

                // We scale the Z coordinate also, to avoid an inversion
                ECFieldElement X2 = X.multiply(scale.square());
                ECFieldElement L2 = L.add(X).add(X2);
                ECFieldElement Z2 = Z.multiply(scale);

                return this.getCurve().createRawPoint(X2, L2, new ECFieldElement[]{ Z2 }, this.withCompression); // earlier JDK
            }
            default:
            {
                return super.scaleX(scale);
            }
            }
        }

        public ECPoint scaleY(ECFieldElement scale)
        {
            if (this.isInfinity())
            {
                return this;
            }

            int coord = this.getCurveCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_LAMBDA_AFFINE:
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                ECFieldElement X = this.getRawXCoord(), L = this.getRawYCoord(); // earlier JDK

                // Y is actually Lambda (X + Y/X) here
                ECFieldElement L2 = L.add(X).multiply(scale).add(X);

                return this.getCurve().createRawPoint(X, L2, this.getRawZCoords(), this.withCompression); // earlier JDK
            }
            default:
            {
                return super.scaleY(scale);
            }
            }
        }

        public ECPoint subtract(ECPoint b)
        {
            if (b.isInfinity())
            {
                return this;
            }

            // Add -b
            return this.add(b.negate());
        }

        public ECPoint.AbstractF2m tau()
        {
            if (this.isInfinity())
            {
                return this;
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            ECFieldElement X1 = this.x;

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            case ECCurve.COORD_LAMBDA_AFFINE:
            {
                ECFieldElement Y1 = this.y;
                return (ECPoint.AbstractF2m)curve.createRawPoint(X1.square(), Y1.square(), this.withCompression);
            }
            case ECCurve.COORD_HOMOGENEOUS:
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                ECFieldElement Y1 = this.y, Z1 = this.zs[0];
                return (ECPoint.AbstractF2m)curve.createRawPoint(X1.square(), Y1.square(),
                    new ECFieldElement[]{ Z1.square() }, this.withCompression);
            }
            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }

        public ECPoint.AbstractF2m tauPow(int pow)
        {
            if (this.isInfinity())
            {
                return this;
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            ECFieldElement X1 = this.x;

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            case ECCurve.COORD_LAMBDA_AFFINE:
            {
                ECFieldElement Y1 = this.y;
                return (ECPoint.AbstractF2m)curve.createRawPoint(X1.squarePow(pow), Y1.squarePow(pow), this.withCompression);
            }
            case ECCurve.COORD_HOMOGENEOUS:
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                ECFieldElement Y1 = this.y, Z1 = this.zs[0];
                return (ECPoint.AbstractF2m)curve.createRawPoint(X1.squarePow(pow), Y1.squarePow(pow),
                    new ECFieldElement[]{ Z1.squarePow(pow) }, this.withCompression);
            }
            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }
    }

    /**
     * Elliptic curve points over F2m
     */
    public static class F2m extends AbstractF2m
    {
        /**
         * @param curve base curve
         * @param x x point
         * @param y y point
         * 
         * @deprecated Use ECCurve.createPoint to construct points
         */
        public F2m(ECCurve curve, ECFieldElement x, ECFieldElement y)
        {
            this(curve, x, y, false);
        }
        
        /**
         * @param curve base curve
         * @param x x point
         * @param y y point
         * @param withCompression true if encode with point compression.
         * 
         * @deprecated per-point compression property will be removed, refer {@link #getEncoded(boolean)}
         */
        public F2m(ECCurve curve, ECFieldElement x, ECFieldElement y, boolean withCompression)
        {
            super(curve, x, y);

            if ((x == null) != (y == null))
            {
                throw new IllegalArgumentException("Exactly one of the field elements is null");
            }

            if (x != null)
            {
                // Check if x and y are elements of the same field
                ECFieldElement.F2m.checkFieldElements(this.x, this.y);

                // Check if x and a are elements of the same field
                if (curve != null)
                {
                    ECFieldElement.F2m.checkFieldElements(this.x, this.curve.getA());
                }
            }

            this.withCompression = withCompression;

//            checkCurveEquation();
        }

        F2m(ECCurve curve, ECFieldElement x, ECFieldElement y, ECFieldElement[] zs, boolean withCompression)
        {
            super(curve, x, y, zs);

            this.withCompression = withCompression;

//            checkCurveEquation();
        }

        protected ECPoint detach()
        {
            return new ECPoint.F2m(null, this.getAffineXCoord(), this.getAffineYCoord()); // earlier JDK
        }

        public ECFieldElement getYCoord()
        {
            int coord = this.getCurveCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_LAMBDA_AFFINE:
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                ECFieldElement X = x, L = y;

                if (this.isInfinity() || X.isZero())
                {
                    return L;
                }

                // Y is actually Lambda (X + Y/X) here; convert to affine value on the fly
                ECFieldElement Y = L.add(X).multiply(X);
                if (ECCurve.COORD_LAMBDA_PROJECTIVE == coord)
                {
                    ECFieldElement Z = zs[0];
                    if (!Z.isOne())
                    {
                        Y = Y.divide(Z);
                    }
                }
                return Y;
            }
            default:
            {
                return y;
            }
            }
        }

        protected boolean getCompressionYTilde()
        {
            ECFieldElement X = this.getRawXCoord();
            if (X.isZero())
            {
                return false;
            }

            ECFieldElement Y = this.getRawYCoord();

            switch (this.getCurveCoordinateSystem())
            {
            case ECCurve.COORD_LAMBDA_AFFINE:
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                // Y is actually Lambda (X + Y/X) here
                return Y.testBitZero() != X.testBitZero();
            }
            default:
            {
                return Y.divide(X).testBitZero();
            }
            }
        }

        public ECPoint add(ECPoint b)
        {
            if (this.isInfinity())
            {
                return b;
            }
            if (b.isInfinity())
            {
                return this;
            }

            ECCurve curve = this.getCurve();
            int coord = curve.getCoordinateSystem();

            ECFieldElement X1 = this.x;
            ECFieldElement X2 = b.x;

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement Y1 = this.y;
                ECFieldElement Y2 = b.y;

                ECFieldElement dx = X1.add(X2), dy = Y1.add(Y2);
                if (dx.isZero())
                {
                    if (dy.isZero())
                    {
                        return twice();
                    }

                    return curve.getInfinity();
                }

                ECFieldElement L = dy.divide(dx);

                ECFieldElement X3 = L.square().add(L).add(dx).add(curve.getA());
                ECFieldElement Y3 = L.multiply(X1.add(X3)).add(X3).add(Y1);

                return new ECPoint.F2m(curve, X3, Y3, this.withCompression);
            }
            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Y1 = this.y, Z1 = this.zs[0];
                ECFieldElement Y2 = b.y, Z2 = b.zs[0];

                boolean Z2IsOne = Z2.isOne();

                ECFieldElement U1 = Z1.multiply(Y2);
                ECFieldElement U2 = Z2IsOne ? Y1 : Y1.multiply(Z2);
                ECFieldElement U = U1.add(U2);
                ECFieldElement V1 = Z1.multiply(X2);
                ECFieldElement V2 = Z2IsOne ? X1 : X1.multiply(Z2);
                ECFieldElement V = V1.add(V2);

                if (V.isZero())
                {
                    if (U.isZero())
                    {
                        return twice();
                    }

                    return curve.getInfinity();
                }

                ECFieldElement VSq = V.square();
                ECFieldElement VCu = VSq.multiply(V);
                ECFieldElement W = Z2IsOne ? Z1 : Z1.multiply(Z2);
                ECFieldElement uv = U.add(V);
                ECFieldElement A = uv.multiplyPlusProduct(U, VSq, curve.getA()).multiply(W).add(VCu);

                ECFieldElement X3 = V.multiply(A);
                ECFieldElement VSqZ2 = Z2IsOne ? VSq : VSq.multiply(Z2);
                ECFieldElement Y3 = U.multiplyPlusProduct(X1, V, Y1).multiplyPlusProduct(VSqZ2, uv, A);
                ECFieldElement Z3 = VCu.multiply(W);

                return new ECPoint.F2m(curve, X3, Y3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                if (X1.isZero())
                {
                    if (X2.isZero())
                    {
                        return curve.getInfinity();
                    }

                    return b.add(this);
                }

                ECFieldElement L1 = this.y, Z1 = this.zs[0];
                ECFieldElement L2 = b.y, Z2 = b.zs[0];

                boolean Z1IsOne = Z1.isOne();
                ECFieldElement U2 = X2, S2 = L2;
                if (!Z1IsOne)
                {
                    U2 = U2.multiply(Z1);
                    S2 = S2.multiply(Z1);
                }

                boolean Z2IsOne = Z2.isOne();
                ECFieldElement U1 = X1, S1 = L1;
                if (!Z2IsOne)
                {
                    U1 = U1.multiply(Z2);
                    S1 = S1.multiply(Z2);
                }

                ECFieldElement A = S1.add(S2);
                ECFieldElement B = U1.add(U2);

                if (B.isZero())
                {
                    if (A.isZero())
                    {
                        return twice();
                    }

                    return curve.getInfinity();
                }

                ECFieldElement X3, L3, Z3;
                if (X2.isZero())
                {
                    // TODO This can probably be optimized quite a bit
                    ECPoint p = this.normalize();
                    X1 = p.getXCoord();
                    ECFieldElement Y1 = p.getYCoord();

                    ECFieldElement Y2 = L2;
                    ECFieldElement L = Y1.add(Y2).divide(X1);

                    X3 = L.square().add(L).add(X1).add(curve.getA());
                    if (X3.isZero())
                    {
                        return new ECPoint.F2m(curve, X3, curve.getB().sqrt(), this.withCompression);
                    }

                    ECFieldElement Y3 = L.multiply(X1.add(X3)).add(X3).add(Y1);
                    L3 = Y3.divide(X3).add(X3);
                    Z3 = curve.fromBigInteger(ECConstants.ONE);
                }
                else
                {
                    B = B.square();
    
                    ECFieldElement AU1 = A.multiply(U1);
                    ECFieldElement AU2 = A.multiply(U2);

                    X3 = AU1.multiply(AU2);
                    if (X3.isZero())
                    {
                        return new ECPoint.F2m(curve, X3, curve.getB().sqrt(), this.withCompression);
                    }

                    ECFieldElement ABZ2 = A.multiply(B);
                    if (!Z2IsOne)
                    {
                        ABZ2 = ABZ2.multiply(Z2);
                    }

                    L3 = AU2.add(B).squarePlusProduct(ABZ2, L1.add(Z1));

                    Z3 = ABZ2;
                    if (!Z1IsOne)
                    {
                        Z3 = Z3.multiply(Z1);
                    }
                }

                return new ECPoint.F2m(curve, X3, L3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }
            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }

        public ECPoint twice()
        {
            if (this.isInfinity()) 
            {
                return this;
            }

            ECCurve curve = this.getCurve();

            ECFieldElement X1 = this.x;
            if (X1.isZero()) 
            {
                // A point with X == 0 is it's own additive inverse
                return curve.getInfinity();
            }

            int coord = curve.getCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement Y1 = this.y;

                ECFieldElement L1 = Y1.divide(X1).add(X1);

                ECFieldElement X3 = L1.square().add(L1).add(curve.getA());
                ECFieldElement Y3 = X1.squarePlusProduct(X3, L1.addOne());

                return new ECPoint.F2m(curve, X3, Y3, this.withCompression);
            }
            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Y1 = this.y, Z1 = this.zs[0];

                boolean Z1IsOne = Z1.isOne();
                ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.multiply(Z1);
                ECFieldElement Y1Z1 = Z1IsOne ? Y1 : Y1.multiply(Z1);

                ECFieldElement X1Sq = X1.square();
                ECFieldElement S = X1Sq.add(Y1Z1);
                ECFieldElement V = X1Z1;
                ECFieldElement vSquared = V.square();
                ECFieldElement sv = S.add(V);
                ECFieldElement h = sv.multiplyPlusProduct(S, vSquared, curve.getA());

                ECFieldElement X3 = V.multiply(h);
                ECFieldElement Y3 = X1Sq.square().multiplyPlusProduct(V, h, sv);
                ECFieldElement Z3 = V.multiply(vSquared);    

                return new ECPoint.F2m(curve, X3, Y3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                ECFieldElement L1 = this.y, Z1 = this.zs[0];

                boolean Z1IsOne = Z1.isOne();
                ECFieldElement L1Z1 = Z1IsOne ? L1 : L1.multiply(Z1);
                ECFieldElement Z1Sq = Z1IsOne ? Z1 : Z1.square();
                ECFieldElement a = curve.getA();
                ECFieldElement aZ1Sq = Z1IsOne ? a : a.multiply(Z1Sq);
                ECFieldElement T = L1.square().add(L1Z1).add(aZ1Sq);
                if (T.isZero())
                {
                    return new ECPoint.F2m(curve, T, curve.getB().sqrt(), withCompression);
                }

                ECFieldElement X3 = T.square();
                ECFieldElement Z3 = Z1IsOne ? T : T.multiply(Z1Sq);

                ECFieldElement b = curve.getB();
                ECFieldElement L3;
                if (b.bitLength() < (curve.getFieldSize() >> 1))
                {
                    ECFieldElement t1 = L1.add(X1).square();
                    ECFieldElement t2;
                    if (b.isOne())
                    {
                        t2 = aZ1Sq.add(Z1Sq).square();
                    }
                    else
                    {
                        // TODO Can be calculated with one square if we pre-compute sqrt(b)
                        t2 = aZ1Sq.squarePlusProduct(b, Z1Sq.square());
                    }
                    L3 = t1.add(T).add(Z1Sq).multiply(t1).add(t2).add(X3);
                    if (a.isZero())
                    {
                        L3 = L3.add(Z3);
                    }
                    else if (!a.isOne())
                    {
                        L3 = L3.add(a.addOne().multiply(Z3));
                    }
                }
                else
                {
                    ECFieldElement X1Z1 = Z1IsOne ? X1 : X1.multiply(Z1);
                    L3 = X1Z1.squarePlusProduct(T, L1Z1).add(X3).add(Z3);
                }

                return new ECPoint.F2m(curve, X3, L3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }
            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }

        public ECPoint twicePlus(ECPoint b)
        {
            if (this.isInfinity()) 
            {
                return b;
            }
            if (b.isInfinity())
            {
                return twice();
            }

            ECCurve curve = this.getCurve();

            ECFieldElement X1 = this.x;
            if (X1.isZero()) 
            {
                // A point with X == 0 is it's own additive inverse
                return b;
            }

            int coord = curve.getCoordinateSystem();

            switch (coord)
            {
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                // NOTE: twicePlus() only optimized for lambda-affine argument
                ECFieldElement X2 = b.x, Z2 = b.zs[0];
                if (X2.isZero() || !Z2.isOne())
                {
                    return twice().add(b);
                }

                ECFieldElement L1 = this.y, Z1 = this.zs[0];
                ECFieldElement L2 = b.y;

                ECFieldElement X1Sq = X1.square();
                ECFieldElement L1Sq = L1.square();
                ECFieldElement Z1Sq = Z1.square();
                ECFieldElement L1Z1 = L1.multiply(Z1);

                ECFieldElement T = curve.getA().multiply(Z1Sq).add(L1Sq).add(L1Z1);
                ECFieldElement L2plus1 = L2.addOne();
                ECFieldElement A = curve.getA().add(L2plus1).multiply(Z1Sq).add(L1Sq).multiplyPlusProduct(T, X1Sq, Z1Sq);
                ECFieldElement X2Z1Sq = X2.multiply(Z1Sq);
                ECFieldElement B = X2Z1Sq.add(T).square();

                if (B.isZero())
                {
                    if (A.isZero())
                    {
                        return b.twice();
                    }

                    return curve.getInfinity();
                }

                if (A.isZero())
                {
                    return new ECPoint.F2m(curve, A, curve.getB().sqrt(), withCompression);
                }

                ECFieldElement X3 = A.square().multiply(X2Z1Sq);
                ECFieldElement Z3 = A.multiply(B).multiply(Z1Sq);
                ECFieldElement L3 = A.add(B).square().multiplyPlusProduct(T, L2plus1, Z3);

                return new ECPoint.F2m(curve, X3, L3, new ECFieldElement[]{ Z3 }, this.withCompression);
            }
            default:
            {
                return twice().add(b);
            }
            }
        }

        public ECPoint negate()
        {
            if (this.isInfinity())
            {
                return this;
            }

            ECFieldElement X = this.x;
            if (X.isZero())
            {
                return this;
            }

            switch (this.getCurveCoordinateSystem())
            {
            case ECCurve.COORD_AFFINE:
            {
                ECFieldElement Y = this.y;
                return new ECPoint.F2m(curve, X, Y.add(X), this.withCompression);
            }
            case ECCurve.COORD_HOMOGENEOUS:
            {
                ECFieldElement Y = this.y, Z = this.zs[0];
                return new ECPoint.F2m(curve, X, Y.add(X), new ECFieldElement[]{ Z }, this.withCompression);
            }
            case ECCurve.COORD_LAMBDA_AFFINE:
            {
                ECFieldElement L = this.y;
                return new ECPoint.F2m(curve, X, L.addOne(), this.withCompression);
            }
            case ECCurve.COORD_LAMBDA_PROJECTIVE:
            {
                // L is actually Lambda (X + Y/X) here
                ECFieldElement L = this.y, Z = this.zs[0];
                return new ECPoint.F2m(curve, X, L.add(Z), new ECFieldElement[]{ Z }, this.withCompression);
            }
            default:
            {
                throw new IllegalStateException("unsupported coordinate system");
            }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy