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

org.opendaylight.netconf.shaded.eddsa.math.GroupElement Maven / Gradle / Ivy

There is a newer version: 8.0.3
Show newest version
/**
 * 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 org.opendaylight.netconf.shaded.eddsa.math;

import org.opendaylight.netconf.shaded.eddsa.Utils;

import java.io.Serializable;
import java.util.Arrays;

/**
 * A point $(x,y)$ on an EdDSA curve.
 * 

* Reviewed/commented by Bloody Rookie ([email protected]) *

* 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. *

    *
  • P2: Projective representation $(X:Y:Z)$ satisfying $x=X/Z, y=Y/Z$. *
  • P3: Extended projective representation $(X:Y:Z:T)$ satisfying $x=X/Z, y=Y/Z, XY=ZT$. *
  • P3PrecomputedDouble: P3 but with dblPrecmp populated. *
  • P1P1: Completed representation $((X:Z), (Y:T))$ satisfying $x=X/Z, y=Y/T$. *
  • PRECOMP: Precomputed representation $(y+x, y-x, 2dxy)$. *
  • CACHED: Cached representation $(Y+X, Y-X, Z, 2dT)$ *
*/ 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: *

    *
  • $x = sign(x) * \sqrt{(y^2 - 1) / (d * y^2 + 1)} = sign(x) * \sqrt{u / v}$ with $u = y^2 - 1$ and $v = d * y^2 + 1$. *
  • Setting $β = (u * v^3) * (u * v^7)^{((q - 5) / 8)}$ one has $β^2 = \pm(u / v)$. *
  • If $v * β = -u$ multiply $β$ with $i=\sqrt{-1}$. *
  • Set $x := β$. *
  • If $sign(x) \ne$ bit 255 of $s$ then negate $x$. *
* * @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: *

    *
  • $x = sign(x) * \sqrt{(y^2 - 1) / (d * y^2 + 1)} = sign(x) * \sqrt{u / v}$ with $u = y^2 - 1$ and $v = d * y^2 + 1$. *
  • Setting $β = (u * v^3) * (u * v^7)^{((q - 5) / 8)}$ one has $β^2 = \pm(u / v)$. *
  • If $v * β = -u$ multiply $β$ with $i=\sqrt{-1}$. *
  • Set $x := β$. *
  • If $sign(x) \ne$ bit 255 of $s$ then negate $x$. *
* * @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: *

    *
  • P3 $\rightarrow$ P2 *
  • P3 $\rightarrow$ CACHED (1 multiply, 1 add, 1 subtract) *
  • P1P1 $\rightarrow$ P2 (3 multiply) *
  • P1P1 $\rightarrow$ P3 (4 multiply) * * @param repr The representation to convert to. * @return A new group element in the given representation. */ private GroupElement toRep(final Representation repr) { switch (this.repr) { case P2: switch (repr) { case P2: return p2(this.curve, this.X, this.Y, this.Z); default: throw new IllegalArgumentException(); } case P3: switch (repr) { case P2: return p2(this.curve, this.X, this.Y, this.Z); case P3: return p3(this.curve, this.X, this.Y, this.Z, this.T); case CACHED: return cached(this.curve, this.Y.add(this.X), this.Y.subtract(this.X), this.Z, this.T.multiply(this.curve.get2D())); default: throw new IllegalArgumentException(); } case P1P1: switch (repr) { case P2: return p2(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T)); case P3: return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y), false); case P3PrecomputedDouble: return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y), true); case P1P1: return p1p1(this.curve, this.X, this.Y, this.Z, this.T); default: throw new IllegalArgumentException(); } case PRECOMP: switch (repr) { case PRECOMP: return precomp(this.curve, this.X, this.Y, this.Z); default: throw new IllegalArgumentException(); } case CACHED: switch (repr) { case CACHED: return cached(this.curve, this.X, this.Y, this.Z, this.T); default: throw new IllegalArgumentException(); } default: throw new UnsupportedOperationException(); } } /** * Precomputes table for {@link #scalarMultiply(byte[])}. */ private GroupElement[][] precomputeSingle() { // Precomputation for single scalar multiplication. GroupElement[][] precmp = new GroupElement[32][8]; // TODO-CR BR: check that this == base point when the method is called. GroupElement Bi = this; for (int i = 0; i < 32; i++) { GroupElement Bij = Bi; for (int j = 0; j < 8; j++) { final FieldElement recip = Bij.Z.invert(); final FieldElement x = Bij.X.multiply(recip); final FieldElement y = Bij.Y.multiply(recip); precmp[i][j] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); Bij = Bij.add(Bi.toCached()).toP3(); } // Only every second summand is precomputed (16^2 = 256) for (int k = 0; k < 8; k++) { Bi = Bi.add(Bi.toCached()).toP3(); } } return precmp; } /** * Precomputes table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}. */ private GroupElement[] precomputeDouble() { // Precomputation for double scalar multiplication. // P,3P,5P,7P,9P,11P,13P,15P GroupElement[] dblPrecmp = new GroupElement[8]; GroupElement Bi = this; for (int i = 0; i < 8; i++) { final FieldElement recip = Bi.Z.invert(); final FieldElement x = Bi.X.multiply(recip); final FieldElement y = Bi.Y.multiply(recip); dblPrecmp[i] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); // Bi = edwards(B,edwards(B,Bi)) Bi = this.add(this.add(Bi.toCached()).toP3().toCached()).toP3(); } return dblPrecmp; } /** * Doubles a given group element $p$ in $P^2$ or $P^3$ representation and returns the result in $P \times P$ representation. * $r = 2 * p$ where $p = (X : Y : Z)$ or $p = (X : Y : Z : T)$ *

    * $r$ in $P \times P$ representation: *

    * $r = ((X' : Z'), (Y' : T'))$ where *

      *
    • $X' = (X + Y)^2 - (Y^2 + X^2)$ *
    • $Y' = Y^2 + X^2$ *
    • $Z' = y^2 - X^2$ *
    • $T' = 2 * Z^2 - (y^2 - X^2)$ *

    * $r$ converted from $P \times P$ to $P^2$ representation: *

    * $r = (X'' : Y'' : Z'')$ where *

      *
    • $X'' = X' * Z' = ((X + Y)^2 - Y^2 - X^2) * (2 * Z^2 - (y^2 - X^2))$ *
    • $Y'' = Y' * T' = (Y^2 + X^2) * (2 * Z^2 - (y^2 - X^2))$ *
    • $Z'' = Z' * T' = (y^2 - X^2) * (2 * Z^2 - (y^2 - X^2))$ *

    * Formula for the $P^2$ representation is in agreement with the formula given in [4] page 12 (with $a = -1$) * up to a common factor -1 which does not matter: *

    * $$ * B = (X + Y)^2; C = X^2; D = Y^2; E = -C = -X^2; F := E + D = Y^2 - X^2; H = Z^2; J = F − 2 * H; \\ * X3 = (B − C − D) · J = X' * (-T'); \\ * Y3 = F · (E − D) = Z' * (-Y'); \\ * Z3 = F · J = Z' * (-T'). * $$ * * @return The P1P1 representation */ public GroupElement dbl() { switch (this.repr) { case P2: case P3: // Ignore T for P3 representation FieldElement XX, YY, B, A, AA, Yn, Zn; XX = this.X.square(); YY = this.Y.square(); B = this.Z.squareAndDouble(); A = this.X.add(this.Y); AA = A.square(); Yn = YY.add(XX); Zn = YY.subtract(XX); return p1p1(this.curve, AA.subtract(Yn), Yn, Zn, B.subtract(Zn)); default: throw new UnsupportedOperationException(); } } /** * GroupElement addition using the twisted Edwards addition law with * extended coordinates (Hisil2008). *

    * this must be in $P^3$ representation and $q$ in PRECOMP representation. * $r = p + q$ where $p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z) = (Y2/Z2 + X2/Z2, Y2/Z2 - X2/Z2, 2 * d * X2/Z2 * Y2/Z2)$ *

    * $r$ in $P \times P$ representation: *

    * $r = ((X' : Z'), (Y' : T'))$ where *

      *
    • $X' = (Y1 + X1) * q.X - (Y1 - X1) * q.Y = ((Y1 + X1) * (Y2 + X2) - (Y1 - X1) * (Y2 - X2)) * 1/Z2$ *
    • $Y' = (Y1 + X1) * q.X + (Y1 - X1) * q.Y = ((Y1 + X1) * (Y2 + X2) + (Y1 - X1) * (Y2 - X2)) * 1/Z2$ *
    • $Z' = 2 * Z1 + T1 * q.Z = 2 * Z1 + T1 * 2 * d * X2 * Y2 * 1/Z2^2 = (2 * Z1 * Z2 + 2 * d * T1 * T2) * 1/Z2$ *
    • $T' = 2 * Z1 - T1 * q.Z = 2 * Z1 - T1 * 2 * d * X2 * Y2 * 1/Z2^2 = (2 * Z1 * Z2 - 2 * d * T1 * T2) * 1/Z2$ *

    * Setting $A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2$ we get *

      *
    • $X' = (B - A) * 1/Z2$ *
    • $Y' = (B + A) * 1/Z2$ *
    • $Z' = (D + C) * 1/Z2$ *
    • $T' = (D - C) * 1/Z2$ *

    * $r$ converted from $P \times P$ to $P^2$ representation: *

    * $r = (X'' : Y'' : Z'' : T'')$ where *

      *
    • $X'' = X' * Z' = (B - A) * (D + C) * 1/Z2^2$ *
    • $Y'' = Y' * T' = (B + A) * (D - C) * 1/Z2^2$ *
    • $Z'' = Z' * T' = (D + C) * (D - C) * 1/Z2^2$ *
    • $T'' = X' * Y' = (B - A) * (B + A) * 1/Z2^2$ *

    * TODO-CR BR: Formula for the $P^2$ representation is not in agreement with the formula given in [2] page 6
    * TODO-CR BR: (the common factor $1/Z2^2$ does not matter):
    * $$ * E = B - A, F = D - C, G = D + C, H = B + A \\ * X3 = E * F = (B - A) * (D - C); \\ * Y3 = G * H = (D + C) * (B + A); \\ * Z3 = F * G = (D - C) * (D + C); \\ * T3 = E * H = (B - A) * (B + A); * $$ * * @param q the PRECOMP representation of the GroupElement to add. * @return the P1P1 representation of the result. */ private GroupElement madd(GroupElement q) { if (this.repr != Representation.P3) throw new UnsupportedOperationException(); if (q.repr != Representation.PRECOMP) throw new IllegalArgumentException(); FieldElement YpX, YmX, A, B, C, D; YpX = this.Y.add(this.X); YmX = this.Y.subtract(this.X); A = YpX.multiply(q.X); // q->y+x B = YmX.multiply(q.Y); // q->y-x C = q.Z.multiply(this.T); // q->2dxy D = this.Z.add(this.Z); return p1p1(this.curve, A.subtract(B), A.add(B), D.add(C), D.subtract(C)); } /** * GroupElement subtraction using the twisted Edwards addition law with * extended coordinates (Hisil2008). *

    * this must be in $P^3$ representation and $q$ in PRECOMP representation. * $r = p - q$ where $p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z) = (Y2/Z2 + X2/Z2, Y2/Z2 - X2/Z2, 2 * d * X2/Z2 * Y2/Z2)$ *

    * Negating $q$ means negating the value of $X2$ and $T2$ (the latter is irrelevant here). * The formula is in accordance to {@link #madd the above addition}. * * @param q the PRECOMP representation of the GroupElement to subtract. * @return the P1P1 representation of the result. */ private GroupElement msub(GroupElement q) { if (this.repr != Representation.P3) throw new UnsupportedOperationException(); if (q.repr != Representation.PRECOMP) throw new IllegalArgumentException(); FieldElement YpX, YmX, A, B, C, D; YpX = this.Y.add(this.X); YmX = this.Y.subtract(this.X); A = YpX.multiply(q.Y); // q->y-x B = YmX.multiply(q.X); // q->y+x C = q.Z.multiply(this.T); // q->2dxy D = this.Z.add(this.Z); return p1p1(this.curve, A.subtract(B), A.add(B), D.subtract(C), D.add(C)); } /** * GroupElement addition using the twisted Edwards addition law with * extended coordinates (Hisil2008). *

    * this must be in $P^3$ representation and $q$ in CACHED representation. * $r = p + q$ where $p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z, q.T) = (Y2 + X2, Y2 - X2, Z2, 2 * d * T2)$ *

    * $r$ in $P \times P$ representation: *

      *
    • $X' = (Y1 + X1) * (Y2 + X2) - (Y1 - X1) * (Y2 - X2)$ *
    • $Y' = (Y1 + X1) * (Y2 + X2) + (Y1 - X1) * (Y2 - X2)$ *
    • $Z' = 2 * Z1 * Z2 + 2 * d * T1 * T2$ *
    • $T' = 2 * Z1 * T2 - 2 * d * T1 * T2$ *

    * Setting $A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2$ we get *

      *
    • $X' = (B - A)$ *
    • $Y' = (B + A)$ *
    • $Z' = (D + C)$ *
    • $T' = (D - C)$ *

    * Same result as in {@link #madd} (up to a common factor which does not matter). * * @param q the CACHED representation of the GroupElement to add. * @return the P1P1 representation of the result. */ public GroupElement add(GroupElement q) { if (this.repr != Representation.P3) throw new UnsupportedOperationException(); if (q.repr != Representation.CACHED) throw new IllegalArgumentException(); FieldElement YpX, YmX, A, B, C, ZZ, D; YpX = this.Y.add(this.X); YmX = this.Y.subtract(this.X); A = YpX.multiply(q.X); // q->Y+X B = YmX.multiply(q.Y); // q->Y-X C = q.T.multiply(this.T); // q->2dT ZZ = this.Z.multiply(q.Z); D = ZZ.add(ZZ); return p1p1(this.curve, A.subtract(B), A.add(B), D.add(C), D.subtract(C)); } /** * GroupElement subtraction using the twisted Edwards addition law with * extended coordinates (Hisil2008). *

    * $r = p - q$ *

    * Negating $q$ means negating the value of the coordinate $X2$ and $T2$. * The formula is in accordance to {@link #add the above addition}. * * @param q the PRECOMP representation of the GroupElement to subtract. * @return the P1P1 representation of the result. */ public GroupElement sub(GroupElement q) { if (this.repr != Representation.P3) throw new UnsupportedOperationException(); if (q.repr != Representation.CACHED) throw new IllegalArgumentException(); FieldElement YpX, YmX, A, B, C, ZZ, D; YpX = Y.add(X); YmX = Y.subtract(X); A = YpX.multiply(q.Y); // q->Y-X B = YmX.multiply(q.X); // q->Y+X C = q.T.multiply(T); // q->2dT ZZ = Z.multiply(q.Z); D = ZZ.add(ZZ); return p1p1(curve, A.subtract(B), A.add(B), D.subtract(C), D.add(C)); } /** * Negates this group element by subtracting it from the neutral group element. *

    * TODO-CR BR: why not simply negate the coordinates $X$ and $T$? * * @return The negative of this group element. */ public GroupElement negate() { if (this.repr != Representation.P3) throw new UnsupportedOperationException(); return this.curve.getZero(Representation.P3).sub(toCached()).toP3PrecomputeDouble(); } @Override public int hashCode() { return Arrays.hashCode(this.toByteArray()); } @Override public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof GroupElement)) return false; GroupElement ge = (GroupElement) obj; if (!this.repr.equals(ge.repr)) { try { ge = ge.toRep(this.repr); } catch (RuntimeException e) { return false; } } switch (this.repr) { case P2: case P3: // Try easy way first if (this.Z.equals(ge.Z)) return this.X.equals(ge.X) && this.Y.equals(ge.Y); // X1/Z1 = X2/Z2 --> X1*Z2 = X2*Z1 final FieldElement x1 = this.X.multiply(ge.Z); final FieldElement y1 = this.Y.multiply(ge.Z); final FieldElement x2 = ge.X.multiply(this.Z); final FieldElement y2 = ge.Y.multiply(this.Z); return x1.equals(x2) && y1.equals(y2); case P1P1: return toP2().equals(ge); case PRECOMP: // Compare directly, PRECOMP is derived directly from x and y return this.X.equals(ge.X) && this.Y.equals(ge.Y) && this.Z.equals(ge.Z); case CACHED: // Try easy way first if (this.Z.equals(ge.Z)) return this.X.equals(ge.X) && this.Y.equals(ge.Y) && this.T.equals(ge.T); // (Y+X)/Z = y+x etc. final FieldElement x3 = this.X.multiply(ge.Z); final FieldElement y3 = this.Y.multiply(ge.Z); final FieldElement t3 = this.T.multiply(ge.Z); final FieldElement x4 = ge.X.multiply(this.Z); final FieldElement y4 = ge.Y.multiply(this.Z); final FieldElement t4 = ge.T.multiply(this.Z); return x3.equals(x4) && y3.equals(y4) && t3.equals(t4); default: return false; } } /** * Convert a to radix 16. *

    * Method is package private only so that tests run. * * @param a $= a[0]+256*a[1]+...+256^{31} a[31]$ * @return 64 bytes, each between -8 and 7 */ static byte[] toRadix16(final byte[] a) { final byte[] e = new byte[64]; int i; // Radix 16 notation for (i = 0; i < 32; i++) { e[2*i+0] = (byte) (a[i] & 15); e[2*i+1] = (byte) ((a[i] >> 4) & 15); } /* each e[i] is between 0 and 15 */ /* e[63] is between 0 and 7 */ int carry = 0; for (i = 0; i < 63; i++) { e[i] += carry; carry = e[i] + 8; carry >>= 4; e[i] -= carry << 4; } e[63] += carry; /* each e[i] is between -8 and 7 */ return e; } /** * Constant-time conditional move. *

    * Replaces this with $u$ if $b == 1$.
    * Replaces this with this if $b == 0$. *

    * Method is package private only so that tests run. * * @param u The group element to return if $b == 1$. * @param b in $\{0, 1\}$ * @return $u$ if $b == 1$; this if $b == 0$. Results undefined if $b$ is not in $\{0, 1\}$. */ GroupElement cmov(final GroupElement u, final int b) { return precomp(curve, X.cmov(u.X, b), Y.cmov(u.Y, b), Z.cmov(u.Z, b)); } /** * Look up $16^i r_i B$ in the precomputed table. *

    * No secret array indices, no secret branching. * Constant time. *

    * Must have previously precomputed. *

    * Method is package private only so that tests run. * * @param pos $= i/2$ for $i$ in $\{0, 2, 4,..., 62\}$ * @param b $= r_i$ * @return the GroupElement */ GroupElement select(final int pos, final int b) { // Is r_i negative? final int bnegative = Utils.negative(b); // |r_i| final int babs = b - (((-bnegative) & b) << 1); // 16^i |r_i| B final GroupElement t = this.curve.getZero(Representation.PRECOMP) .cmov(this.precmp[pos][0], Utils.equal(babs, 1)) .cmov(this.precmp[pos][1], Utils.equal(babs, 2)) .cmov(this.precmp[pos][2], Utils.equal(babs, 3)) .cmov(this.precmp[pos][3], Utils.equal(babs, 4)) .cmov(this.precmp[pos][4], Utils.equal(babs, 5)) .cmov(this.precmp[pos][5], Utils.equal(babs, 6)) .cmov(this.precmp[pos][6], Utils.equal(babs, 7)) .cmov(this.precmp[pos][7], Utils.equal(babs, 8)); // -16^i |r_i| B final GroupElement tminus = precomp(curve, t.Y, t.X, t.Z.negate()); // 16^i r_i B return t.cmov(tminus, bnegative); } /** * $h = a * B$ where $a = a[0]+256*a[1]+\dots+256^{31} a[31]$ and * $B$ is this point. If its lookup table has not been precomputed, it * will be at the start of the method (and cached for later calls). * Constant time. *

    * Preconditions: (TODO: Check this applies here) * $a[31] \le 127$ * @param a $= a[0]+256*a[1]+\dots+256^{31} a[31]$ * @return the GroupElement */ public GroupElement scalarMultiply(final byte[] a) { GroupElement t; int i; final byte[] e = toRadix16(a); GroupElement h = this.curve.getZero(Representation.P3); for (i = 1; i < 64; i += 2) { t = select(i/2, e[i]); h = h.madd(t).toP3(); } h = h.dbl().toP2().dbl().toP2().dbl().toP2().dbl().toP3(); for (i = 0; i < 64; i += 2) { t = select(i/2, e[i]); h = h.madd(t).toP3(); } return h; } /** * Calculates a sliding-windows base 2 representation for a given value $a$. * To learn more about it see [6] page 8. *

    * Output: $r$ which satisfies * $a = r0 * 2^0 + r1 * 2^1 + \dots + r255 * 2^{255}$ with $ri$ in $\{-15, -13, -11, -9, -7, -5, -3, -1, 0, 1, 3, 5, 7, 9, 11, 13, 15\}$ *

    * Method is package private only so that tests run. * * @param a $= a[0]+256*a[1]+\dots+256^{31} a[31]$. * @return The byte array $r$ in the above described form. */ static byte[] slide(final byte[] a) { byte[] r = new byte[256]; // Put each bit of 'a' into a separate byte, 0 or 1 for (int i = 0; i < 256; ++i) { r[i] = (byte) (1 & (a[i >> 3] >> (i & 7))); } // Note: r[i] will always be odd. for (int i = 0; i < 256; ++i) { if (r[i] != 0) { for (int b = 1; b <= 6 && i + b < 256; ++b) { // Accumulate bits if possible if (r[i + b] != 0) { if (r[i] + (r[i + b] << b) <= 15) { r[i] += r[i + b] << b; r[i + b] = 0; } else if (r[i] - (r[i + b] << b) >= -15) { r[i] -= r[i + b] << b; for (int k = i + b; k < 256; ++k) { if (r[k] == 0) { r[k] = 1; break; } r[k] = 0; } } else break; } } } } return r; } /** * $r = a * A + b * B$ where $a = a[0]+256*a[1]+\dots+256^{31} a[31]$, * $b = b[0]+256*b[1]+\dots+256^{31} b[31]$ and $B$ is this point. *

    * $A$ must have been previously precomputed. * * @param A in P3 representation. * @param a $= a[0]+256*a[1]+\dots+256^{31} a[31]$ * @param b $= b[0]+256*b[1]+\dots+256^{31} b[31]$ * @return the GroupElement */ public GroupElement doubleScalarMultiplyVariableTime(final GroupElement A, final byte[] a, final byte[] b) { // TODO-CR BR: A check that this is the base point is needed. final byte[] aslide = slide(a); final byte[] bslide = slide(b); GroupElement r = this.curve.getZero(Representation.P2); int i; for (i = 255; i >= 0; --i) { if (aslide[i] != 0 || bslide[i] != 0) break; } for (; i >= 0; --i) { GroupElement t = r.dbl(); if (aslide[i] > 0) { t = t.toP3().madd(A.dblPrecmp[aslide[i]/2]); } else if(aslide[i] < 0) { t = t.toP3().msub(A.dblPrecmp[(-aslide[i])/2]); } if (bslide[i] > 0) { t = t.toP3().madd(this.dblPrecmp[bslide[i]/2]); } else if(bslide[i] < 0) { t = t.toP3().msub(this.dblPrecmp[(-bslide[i])/2]); } r = t.toP2(); } return r; } /** * Verify that a point is on its curve. * @return true if the point lies on its curve. */ public boolean isOnCurve() { return isOnCurve(curve); } /** * Verify that a point is on the curve. * @param curve The curve to check. * @return true if the point lies on the curve. */ public boolean isOnCurve(Curve curve) { switch (repr) { case P2: case P3: FieldElement recip = Z.invert(); FieldElement x = X.multiply(recip); FieldElement y = Y.multiply(recip); FieldElement xx = x.square(); FieldElement yy = y.square(); FieldElement dxxyy = curve.getD().multiply(xx).multiply(yy); return curve.getField().ONE.add(dxxyy).add(xx).equals(yy); default: return toP2().isOnCurve(curve); } } @Override public String toString() { return "[GroupElement\nX="+X+"\nY="+Y+"\nZ="+Z+"\nT="+T+"\n]"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy