/** * EdDSA-Java by str4d * * To the extent possible under law, the person who associated CC0 with * EdDSA-Java has waived all copyright and related or neighboring rights * to EdDSA-Java. * * You should have received a copy of the CC0 legalcode along with this * work. If not, see . * */ package net.i2p.crypto.eddsa.math; import net.i2p.crypto.eddsa.Utils; import java.io.Serializable; import java.util.Arrays; /** * A point $(x,y)$ on an EdDSA curve. *

* Reviewed/commented by Bloody Rookie (nemproject@gmx.de) *

* Literature:
* [1] Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter Schwabe and Bo-Yin Yang : High-speed high-security signatures
* [2] Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, Ed Dawson: Twisted Edwards Curves Revisited
* [3] Daniel J. Bernsteina, Tanja Lange: A complete set of addition laws for incomplete Edwards curves
* [4] Daniel J. Bernstein, Peter Birkner, Marc Joye, Tanja Lange and Christiane Peters: Twisted Edwards Curves
* [5] Christiane Pascale Peters: Curves, Codes, and Cryptography (PhD thesis)
* [6] Daniel J. Bernstein, Peter Birkner, Tanja Lange and Christiane Peters: Optimizing double-base elliptic-curve single-scalar multiplication
* * @author str4d */ public class GroupElement implements Serializable { private static final long serialVersionUID = 2395879087349587L; /** * Available representations for a group element. *

*/ public enum Representation { /** Projective ($P^2$): $(X:Y:Z)$ satisfying $x=X/Z, y=Y/Z$ */ P2, /** Extended ($P^3$): $(X:Y:Z:T)$ satisfying $x=X/Z, y=Y/Z, XY=ZT$ */ P3, /** Can only be requested. Results in P3 representation but also populates dblPrecmp. */ P3PrecomputedDouble, /** Completed ($P \times P$): $((X:Z),(Y:T))$ satisfying $x=X/Z, y=Y/T$ */ P1P1, /** Precomputed (Duif): $(y+x,y-x,2dxy)$ */ PRECOMP, /** Cached: $(Y+X,Y-X,Z,2dT)$ */ CACHED } /** * Creates a new group element in P2 representation. * * @param curve The curve. * @param X The $X$ coordinate. * @param Y The $Y$ coordinate. * @param Z The $Z$ coordinate. * @return The group element in P2 representation. */ public static GroupElement p2( final Curve curve, final FieldElement X, final FieldElement Y, final FieldElement Z) { return new GroupElement(curve, Representation.P2, X, Y, Z, null); } /** * Creates a new group element in P3 representation, without pre-computation. * * @param curve The curve. * @param X The $X$ coordinate. * @param Y The $Y$ coordinate. * @param Z The $Z$ coordinate. * @param T The $T$ coordinate. * @return The group element in P3 representation. */ public static GroupElement p3( final Curve curve, final FieldElement X, final FieldElement Y, final FieldElement Z, final FieldElement T) { return p3(curve, X, Y, Z, T, false); } /** * Creates a new group element in P3 representation, potentially with pre-computation. * * @param curve The curve. * @param X The $X$ coordinate. * @param Y The $Y$ coordinate. * @param Z The $Z$ coordinate. * @param T The $T$ coordinate. * @param precomputeDoubleOnly If true, populate dblPrecmp, else set to null. * @return The group element in P3 representation. */ public static GroupElement p3( final Curve curve, final FieldElement X, final FieldElement Y, final FieldElement Z, final FieldElement T, final boolean precomputeDoubleOnly) { return new GroupElement(curve, Representation.P3, X, Y, Z, T, precomputeDoubleOnly); } /** * Creates a new group element in P1P1 representation. * * @param curve The curve. * @param X The $X$ coordinate. * @param Y The $Y$ coordinate. * @param Z The $Z$ coordinate. * @param T The $T$ coordinate. * @return The group element in P1P1 representation. */ public static GroupElement p1p1( final Curve curve, final FieldElement X, final FieldElement Y, final FieldElement Z, final FieldElement T) { return new GroupElement(curve, Representation.P1P1, X, Y, Z, T); } /** * Creates a new group element in PRECOMP representation. * * @param curve The curve. * @param ypx The $y + x$ value. * @param ymx The $y - x$ value. * @param xy2d The $2 * d * x * y$ value. * @return The group element in PRECOMP representation. */ public static GroupElement precomp( final Curve curve, final FieldElement ypx, final FieldElement ymx, final FieldElement xy2d) { return new GroupElement(curve, Representation.PRECOMP, ypx, ymx, xy2d, null); } /** * Creates a new group element in CACHED representation. * * @param curve The curve. * @param YpX The $Y + X$ value. * @param YmX The $Y - X$ value. * @param Z The $Z$ coordinate. * @param T2d The $2 * d * T$ value. * @return The group element in CACHED representation. */ public static GroupElement cached( final Curve curve, final FieldElement YpX, final FieldElement YmX, final FieldElement Z, final FieldElement T2d) { return new GroupElement(curve, Representation.CACHED, YpX, YmX, Z, T2d); } /** * Variable is package private only so that tests run. */ final Curve curve; /** * Variable is package private only so that tests run. */ final Representation repr; /** * Variable is package private only so that tests run. */ final FieldElement X; /** * Variable is package private only so that tests run. */ final FieldElement Y; /** * Variable is package private only so that tests run. */ final FieldElement Z; /** * Variable is package private only so that tests run. */ final FieldElement T; /** * Precomputed table for {@link #scalarMultiply(byte[])}, * filled if necessary. *

* Variable is package private only so that tests run. */ final GroupElement[][] precmp; /** * Precomputed table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}, * filled if necessary. *

* Variable is package private only so that tests run. */ final GroupElement[] dblPrecmp; /** * Creates a group element for a curve, without any pre-computation. * * @param curve The curve. * @param repr The representation used to represent the group element. * @param X The $X$ coordinate. * @param Y The $Y$ coordinate. * @param Z The $Z$ coordinate. * @param T The $T$ coordinate. */ public GroupElement( final Curve curve, final Representation repr, final FieldElement X, final FieldElement Y, final FieldElement Z, final FieldElement T) { this(curve, repr, X, Y, Z, T, false); } /** * Creates a group element for a curve, with optional pre-computation. * * @param curve The curve. * @param repr The representation used to represent the group element. * @param X The $X$ coordinate. * @param Y The $Y$ coordinate. * @param Z The $Z$ coordinate. * @param T The $T$ coordinate. * @param precomputeDouble If true, populate dblPrecmp, else set to null. */ public GroupElement( final Curve curve, final Representation repr, final FieldElement X, final FieldElement Y, final FieldElement Z, final FieldElement T, final boolean precomputeDouble) { this.curve = curve; this.repr = repr; this.X = X; this.Y = Y; this.Z = Z; this.T = T; this.precmp = null; this.dblPrecmp = precomputeDouble ? precomputeDouble() : null; } /** * Creates a group element for a curve from a given encoded point. No pre-computation. *

* A point $(x,y)$ is encoded by storing $y$ in bit 0 to bit 254 and the sign of $x$ in bit 255. * $x$ is recovered in the following way: *

* * @param curve The curve. * @param s The encoded point. */ public GroupElement(final Curve curve, final byte[] s) { this(curve, s, false); } /** * Creates a group element for a curve from a given encoded point. With optional pre-computation. *

* A point $(x,y)$ is encoded by storing $y$ in bit 0 to bit 254 and the sign of $x$ in bit 255. * $x$ is recovered in the following way: *

* * @param curve The curve. * @param s The encoded point. * @param precomputeSingleAndDouble If true, populate both precmp and dblPrecmp, else set both to null. */ public GroupElement(final Curve curve, final byte[] s, boolean precomputeSingleAndDouble) { FieldElement x, y, yy, u, v, v3, vxx, check; y = curve.getField().fromByteArray(s); yy = y.square(); // u = y^2-1 u = yy.subtractOne(); // v = dy^2+1 v = yy.multiply(curve.getD()).addOne(); // v3 = v^3 v3 = v.square().multiply(v); // x = (v3^2)vu, aka x = uv^7 x = v3.square().multiply(v).multiply(u); // x = (uv^7)^((q-5)/8) x = x.pow22523(); // x = uv^3(uv^7)^((q-5)/8) x = v3.multiply(u).multiply(x); vxx = x.square().multiply(v); check = vxx.subtract(u); // vx^2-u if (check.isNonZero()) { check = vxx.add(u); // vx^2+u if (check.isNonZero()) throw new IllegalArgumentException("not a valid GroupElement"); x = x.multiply(curve.getI()); } if ((x.isNegative() ? 1 : 0) != Utils.bit(s, curve.getField().getb()-1)) { x = x.negate(); } this.curve = curve; this.repr = Representation.P3; this.X = x; this.Y = y; this.Z = curve.getField().ONE; this.T = this.X.multiply(this.Y); if(precomputeSingleAndDouble) { precmp = precomputeSingle(); dblPrecmp = precomputeDouble(); } else { precmp = null; dblPrecmp = null; } } /** * Gets the curve of the group element. * * @return The curve. */ public Curve getCurve() { return this.curve; } /** * Gets the representation of the group element. * * @return The representation. */ public Representation getRepresentation() { return this.repr; } /** * Gets the $X$ value of the group element. * This is for most representation the projective $X$ coordinate. * * @return The $X$ value. */ public FieldElement getX() { return this.X; } /** * Gets the $Y$ value of the group element. * This is for most representation the projective $Y$ coordinate. * * @return The $Y$ value. */ public FieldElement getY() { return this.Y; } /** * Gets the $Z$ value of the group element. * This is for most representation the projective $Z$ coordinate. * * @return The $Z$ value. */ public FieldElement getZ() { return this.Z; } /** * Gets the $T$ value of the group element. * This is for most representation the projective $T$ coordinate. * * @return The $T$ value. */ public FieldElement getT() { return this.T; } /** * Converts the group element to an encoded point on the curve. * * @return The encoded point as byte array. */ public byte[] toByteArray() { switch (this.repr) { case P2: case P3: FieldElement recip = Z.invert(); FieldElement x = X.multiply(recip); FieldElement y = Y.multiply(recip); byte[] s = y.toByteArray(); s[s.length-1] |= (x.isNegative() ? (byte) 0x80 : 0); return s; default: return toP2().toByteArray(); } } /** * Converts the group element to the P2 representation. * * @return The group element in the P2 representation. */ public GroupElement toP2() { return toRep(Representation.P2); } /** * Converts the group element to the P3 representation. * * @return The group element in the P3 representation. */ public GroupElement toP3() { return toRep(Representation.P3); } /** * Converts the group element to the P3 representation, with dblPrecmp populated. * * @return The group element in the P3 representation. */ public GroupElement toP3PrecomputeDouble() { return toRep(Representation.P3PrecomputedDouble); } /** * Converts the group element to the CACHED representation. * * @return The group element in the CACHED representation. */ public GroupElement toCached() { return toRep(Representation.CACHED); } /** * Convert a GroupElement from one Representation to another. * TODO-CR: Add additional conversion? * $r = p$ *

* Supported conversions: *