| // Copyright 2017 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package com.google.crypto.tink.subtle; |
| |
| import com.google.crypto.tink.annotations.Alpha; |
| import java.security.InvalidKeyException; |
| import java.util.Arrays; |
| |
| /** |
| * This class implements point arithmetic on the elliptic curve <a |
| * href="https://cr.yp.to/ecdh/curve25519-20060209.pdf">Curve25519</a>. |
| * |
| * <p>This class only implements point arithmetic, if you want to use the ECDH Curve25519 function, |
| * please checkout {@link com.google.crypto.tink.subtle.X25519}. |
| * |
| * <p>This implementation is based on <a |
| * href="https://github.com/agl/curve25519-donna/blob/master/curve25519-donna.c">curve255-donna C |
| * implementation</a>. |
| */ |
| @Alpha |
| final class Curve25519 { |
| // https://cr.yp.to/ecdh.html#validate doesn't recommend validating peer's public key. However, |
| // validating public key doesn't harm security and in certain cases, prevents unwanted edge |
| // cases. |
| // As we clear the most significant bit of peer's public key, we don't have to include public keys |
| // that are larger than 2^255. |
| static final byte[][] BANNED_PUBLIC_KEYS = |
| new byte[][] { |
| // 0 |
| new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 |
| }, |
| // 1 |
| new byte[] { |
| (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| }, |
| // 325606250916557431795983626356110631294008115727848805560023387167927233504 |
| new byte[] { |
| (byte) 0xe0, (byte) 0xeb, (byte) 0x7a, (byte) 0x7c, |
| (byte) 0x3b, (byte) 0x41, (byte) 0xb8, (byte) 0xae, |
| (byte) 0x16, (byte) 0x56, (byte) 0xe3, (byte) 0xfa, |
| (byte) 0xf1, (byte) 0x9f, (byte) 0xc4, (byte) 0x6a, |
| (byte) 0xda, (byte) 0x09, (byte) 0x8d, (byte) 0xeb, |
| (byte) 0x9c, (byte) 0x32, (byte) 0xb1, (byte) 0xfd, |
| (byte) 0x86, (byte) 0x62, (byte) 0x05, (byte) 0x16, |
| (byte) 0x5f, (byte) 0x49, (byte) 0xb8, (byte) 0x00, |
| }, |
| // 39382357235489614581723060781553021112529911719440698176882885853963445705823 |
| new byte[] { |
| (byte) 0x5f, (byte) 0x9c, (byte) 0x95, (byte) 0xbc, |
| (byte) 0xa3, (byte) 0x50, (byte) 0x8c, (byte) 0x24, |
| (byte) 0xb1, (byte) 0xd0, (byte) 0xb1, (byte) 0x55, |
| (byte) 0x9c, (byte) 0x83, (byte) 0xef, (byte) 0x5b, |
| (byte) 0x04, (byte) 0x44, (byte) 0x5c, (byte) 0xc4, |
| (byte) 0x58, (byte) 0x1c, (byte) 0x8e, (byte) 0x86, |
| (byte) 0xd8, (byte) 0x22, (byte) 0x4e, (byte) 0xdd, |
| (byte) 0xd0, (byte) 0x9f, (byte) 0x11, (byte) 0x57 |
| }, |
| // 2^255 - 19 - 1 |
| new byte[] { |
| (byte) 0xec, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, |
| }, |
| // 2^255 - 19 |
| new byte[] { |
| (byte) 0xed, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f |
| }, |
| // 2^255 - 19 + 1 |
| new byte[] { |
| (byte) 0xee, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f |
| } |
| }; |
| /** |
| * Computes Montgomery's double-and-add formulas. |
| * |
| * <p>On entry and exit, the absolute value of the limbs of all inputs and outputs are < 2^26. |
| * |
| * @param x2 x projective coordinate of output 2Q, long form |
| * @param z2 z projective coordinate of output 2Q, long form |
| * @param x3 x projective coordinate of output Q + Q', long form |
| * @param z3 z projective coordinate of output Q + Q', long form |
| * @param x x projective coordinate of input Q, short form, destroyed |
| * @param z z projective coordinate of input Q, short form, destroyed |
| * @param xprime x projective coordinate of input Q', short form, destroyed |
| * @param zprime z projective coordinate of input Q', short form, destroyed |
| * @param qmqp input Q - Q', short form, preserved |
| */ |
| private static void monty( |
| long[] x2, |
| long[] z2, |
| long[] x3, |
| long[] z3, |
| long[] x, |
| long[] z, |
| long[] xprime, |
| long[] zprime, |
| long[] qmqp) { |
| long[] origx = Arrays.copyOf(x, Field25519.LIMB_CNT); |
| long[] zzz = new long[19]; |
| long[] xx = new long[19]; |
| long[] zz = new long[19]; |
| long[] xxprime = new long[19]; |
| long[] zzprime = new long[19]; |
| long[] zzzprime = new long[19]; |
| long[] xxxprime = new long[19]; |
| |
| Field25519.sum(x, z); |
| // |x[i]| < 2^27 |
| Field25519.sub(z, origx); // does x - z |
| // |z[i]| < 2^27 |
| |
| long[] origxprime = Arrays.copyOf(xprime, Field25519.LIMB_CNT); |
| Field25519.sum(xprime, zprime); |
| // |xprime[i]| < 2^27 |
| Field25519.sub(zprime, origxprime); |
| // |zprime[i]| < 2^27 |
| Field25519.product(xxprime, xprime, z); |
| // |xxprime[i]| < 14*2^54: the largest product of two limbs will be < 2^(27+27) and {@ref |
| // Field25519#product} adds together, at most, 14 of those products. (Approximating that to |
| // 2^58 doesn't work out.) |
| Field25519.product(zzprime, x, zprime); |
| // |zzprime[i]| < 14*2^54 |
| Field25519.reduceSizeByModularReduction(xxprime); |
| Field25519.reduceCoefficients(xxprime); |
| // |xxprime[i]| < 2^26 |
| Field25519.reduceSizeByModularReduction(zzprime); |
| Field25519.reduceCoefficients(zzprime); |
| // |zzprime[i]| < 2^26 |
| System.arraycopy(xxprime, 0, origxprime, 0, Field25519.LIMB_CNT); |
| Field25519.sum(xxprime, zzprime); |
| // |xxprime[i]| < 2^27 |
| Field25519.sub(zzprime, origxprime); |
| // |zzprime[i]| < 2^27 |
| Field25519.square(xxxprime, xxprime); |
| // |xxxprime[i]| < 2^26 |
| Field25519.square(zzzprime, zzprime); |
| // |zzzprime[i]| < 2^26 |
| Field25519.product(zzprime, zzzprime, qmqp); |
| // |zzprime[i]| < 14*2^52 |
| Field25519.reduceSizeByModularReduction(zzprime); |
| Field25519.reduceCoefficients(zzprime); |
| // |zzprime[i]| < 2^26 |
| System.arraycopy(xxxprime, 0, x3, 0, Field25519.LIMB_CNT); |
| System.arraycopy(zzprime, 0, z3, 0, Field25519.LIMB_CNT); |
| |
| Field25519.square(xx, x); |
| // |xx[i]| < 2^26 |
| Field25519.square(zz, z); |
| // |zz[i]| < 2^26 |
| Field25519.product(x2, xx, zz); |
| // |x2[i]| < 14*2^52 |
| Field25519.reduceSizeByModularReduction(x2); |
| Field25519.reduceCoefficients(x2); |
| // |x2[i]| < 2^26 |
| Field25519.sub(zz, xx); // does zz = xx - zz |
| // |zz[i]| < 2^27 |
| Arrays.fill(zzz, Field25519.LIMB_CNT, zzz.length - 1, 0); |
| Field25519.scalarProduct(zzz, zz, 121665); |
| // |zzz[i]| < 2^(27+17) |
| // No need to call reduceSizeByModularReduction here: scalarProduct doesn't increase the degree |
| // of its input. |
| Field25519.reduceCoefficients(zzz); |
| // |zzz[i]| < 2^26 |
| Field25519.sum(zzz, xx); |
| // |zzz[i]| < 2^27 |
| Field25519.product(z2, zz, zzz); |
| // |z2[i]| < 14*2^(26+27) |
| Field25519.reduceSizeByModularReduction(z2); |
| Field25519.reduceCoefficients(z2); |
| // |z2|i| < 2^26 |
| } |
| |
| /** |
| * Conditionally swap two reduced-form limb arrays if {@code iswap} is 1, but leave them unchanged |
| * if {@code iswap} is 0. Runs in data-invariant time to avoid side-channel attacks. |
| * |
| * <p>NOTE that this function requires that {@code iswap} be 1 or 0; other values give wrong |
| * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the |
| * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must |
| * have magnitude less than Integer.MAX_VALUE. |
| */ |
| static void swapConditional(long[] a, long[] b, int iswap) { |
| int swap = -iswap; |
| for (int i = 0; i < Field25519.LIMB_CNT; i++) { |
| int x = swap & (((int) a[i]) ^ ((int) b[i])); |
| a[i] = ((int) a[i]) ^ x; |
| b[i] = ((int) b[i]) ^ x; |
| } |
| } |
| |
| /** |
| * Conditionally copies a reduced-form limb arrays {@code b} into {@code a} if {@code icopy} is 1, |
| * but leave {@code a} unchanged if 'iswap' is 0. Runs in data-invariant time to avoid |
| * side-channel attacks. |
| * |
| * <p>NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong |
| * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the |
| * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must |
| * have magnitude less than Integer.MAX_VALUE. |
| */ |
| static void copyConditional(long[] a, long[] b, int icopy) { |
| int copy = -icopy; |
| for (int i = 0; i < Field25519.LIMB_CNT; i++) { |
| int x = copy & (((int) a[i]) ^ ((int) b[i])); |
| a[i] = ((int) a[i]) ^ x; |
| } |
| } |
| |
| /** |
| * Calculates nQ where Q is the x-coordinate of a point on the curve. |
| * |
| * @param resultx the x projective coordinate of the resulting curve point (short form). |
| * @param n a little endian, 32-byte number. |
| * @param qBytes a little endian, 32-byte number representing the public point' x coordinate. |
| * @throws InvalidKeyException iff the public key is in the banned list or its length is not |
| * 32-byte. |
| * @throws IllegalStateException iff there is arithmetic error. |
| */ |
| static void curveMult(long[] resultx, byte[] n, byte[] qBytes) throws InvalidKeyException { |
| validatePubKeyAndClearMsb(qBytes); |
| |
| long[] q = Field25519.expand(qBytes); |
| long[] nqpqx = new long[19]; |
| long[] nqpqz = new long[19]; |
| nqpqz[0] = 1; |
| long[] nqx = new long[19]; |
| nqx[0] = 1; |
| long[] nqz = new long[19]; |
| long[] nqpqx2 = new long[19]; |
| long[] nqpqz2 = new long[19]; |
| nqpqz2[0] = 1; |
| long[] nqx2 = new long[19]; |
| long[] nqz2 = new long[19]; |
| nqz2[0] = 1; |
| long[] t = new long[19]; |
| |
| System.arraycopy(q, 0, nqpqx, 0, Field25519.LIMB_CNT); |
| |
| for (int i = 0; i < Field25519.FIELD_LEN; i++) { |
| int b = n[Field25519.FIELD_LEN - i - 1] & 0xff; |
| for (int j = 0; j < 8; j++) { |
| int bit = (b >> (7 - j)) & 1; |
| |
| swapConditional(nqx, nqpqx, bit); |
| swapConditional(nqz, nqpqz, bit); |
| monty(nqx2, nqz2, nqpqx2, nqpqz2, nqx, nqz, nqpqx, nqpqz, q); |
| swapConditional(nqx2, nqpqx2, bit); |
| swapConditional(nqz2, nqpqz2, bit); |
| |
| t = nqx; |
| nqx = nqx2; |
| nqx2 = t; |
| t = nqz; |
| nqz = nqz2; |
| nqz2 = t; |
| t = nqpqx; |
| nqpqx = nqpqx2; |
| nqpqx2 = t; |
| t = nqpqz; |
| nqpqz = nqpqz2; |
| nqpqz2 = t; |
| } |
| } |
| |
| // Computes nqx/nqz. |
| long[] zmone = new long[Field25519.LIMB_CNT]; |
| Field25519.inverse(zmone, nqz); |
| Field25519.mult(resultx, nqx, zmone); |
| |
| // Nowadays it should be standard to protect public key crypto against flaws. I.e. if there is a |
| // computation error through a faulty CPU or if the implementation contains a bug, then if |
| // possible this should be detected at run time. |
| // |
| // The situation is a bit more tricky for X25519 where for example the implementation |
| // proposed in https://tools.ietf.org/html/rfc7748 only uses the x-coordinate. However, a |
| // verification is still possible, but depends on the actual computation. |
| // |
| // Tink's Java implementation is equivalent to RFC7748. We will use the loop invariant in the |
| // Montgomery ladder to detect fault computation. In particular, we use the following invariant: |
| // q, resultx, nqpqx/nqpqx are x coordinates of 3 collinear points q, n*q, (n + 1)*q. |
| if (!isCollinear(q, resultx, nqpqx, nqpqz)) { |
| throw new IllegalStateException( |
| "Arithmetic error in curve multiplication with the public key: " |
| + Hex.encode(qBytes)); |
| } |
| } |
| |
| /** |
| * Validates public key and clear its most significant bit. |
| * |
| * @throws InvalidKeyException iff the {@code pubKey} is in the banned list or its length is not |
| * 32-byte. |
| */ |
| private static void validatePubKeyAndClearMsb(byte[] pubKey) throws InvalidKeyException { |
| if (pubKey.length != 32) { |
| throw new InvalidKeyException("Public key length is not 32-byte"); |
| } |
| // Clears the most significant bit as in the method decodeUCoordinate() of RFC7748. |
| pubKey[31] &= (byte) 0x7f; |
| |
| for (int i = 0; i < BANNED_PUBLIC_KEYS.length; i++) { |
| if (Bytes.equal(BANNED_PUBLIC_KEYS[i], pubKey)) { |
| throw new InvalidKeyException("Banned public key: " + Hex.encode(BANNED_PUBLIC_KEYS[i])); |
| } |
| } |
| } |
| |
| /** |
| * Checks whether there are three collinear points with x coordinate x1, x2, x3/z3. |
| * |
| * @return true if three collinear points with x coordianate x1, x2, x3/z3 are collinear. |
| */ |
| private static boolean isCollinear(long[] x1, long[] x2, long[] x3, long[] z3) { |
| // If x1, x2, x3 (in this method x3 is represented as x3/z3) are the x-coordinates of three |
| // collinear points on a curve, then they satisfy the equation |
| // y^2 = x^3 + ax^2 + x |
| // They also satisfy the equation |
| // 0 = (x - x1)(x - x2)(x - x3) |
| // = x^3 + Ax^2 + Bx + C |
| // where |
| // A = - x1 - x2 - x3 |
| // B = x1*x2 + x2*x3 + x3*x1 |
| // C = - x1*x2*x3 |
| // Hence, the three points also satisfy |
| // y^2 = (a - A)x^2 + (1 - B)x - C |
| // This is a quadratic curve. Three distinct collinear points can only be on a quadratic |
| // curve if the quadratic curve has a line as component. And if a quadratic curve has a line |
| // as component then its discriminant is 0. |
| // Therefore, discriminant((a - A)x^2 + (1-B)x - C) = 0. |
| // In particular: |
| // a = 486662 |
| // lhs = 4 * ((x1 + x2 + a) * z3 + x3) * (x1 * x2 * x3) |
| // rhs = ((x1 * x2 - 1) * z3 + x3 * (x1 + x2))**2 |
| // assert (lhs - rhs) == 0 |
| // |
| // There are 2 cases that we haven't discussed: |
| // |
| // * If x1 and x2 are both points with y-coordinate 0 then the argument doesn't hold. |
| // However, our ECDH computation doesn't allow points of low order (see {@code |
| // validatePublicKey}). Therefore, this edge case never happen. |
| // |
| // * x1, x2 or x3/y3 may be points on the twist. If so, they satisfy the equation |
| // 2y^2 = x^3 + ax^2 + x |
| // Hence, the three points also satisfy |
| // 2y^2 = (a - A)x^2 + (1 - B)x - C |
| // Thus, this collinear check should work for this case too. |
| long[] x1multx2 = new long[Field25519.LIMB_CNT]; |
| long[] x1addx2 = new long[Field25519.LIMB_CNT]; |
| long[] lhs = new long[Field25519.LIMB_CNT + 1]; |
| long[] t = new long[Field25519.LIMB_CNT + 1]; |
| long[] t2 = new long[Field25519.LIMB_CNT + 1]; |
| Field25519.mult(x1multx2, x1, x2); |
| Field25519.sum(x1addx2, x1, x2); |
| long[] a = new long[Field25519.LIMB_CNT]; |
| a[0] = 486662; |
| // t = x1 + x2 + a |
| Field25519.sum(t, x1addx2, a); |
| // t = (x1 + x2 + a) * z3 |
| Field25519.mult(t, t, z3); |
| // t = (x1 + x2 + a) * z3 + x3 |
| Field25519.sum(t, x3); |
| // t = ((x1 + x2 + a) * z3 + x3) * x1 * x2 |
| Field25519.mult(t, t, x1multx2); |
| // t = ((x1 + x2 + a) * z3 + x3) * (x1 * x2 * x3) |
| Field25519.mult(t, t, x3); |
| Field25519.scalarProduct(lhs, t, 4); |
| Field25519.reduceCoefficients(lhs); |
| |
| // t = x1 * x2 * z3 |
| Field25519.mult(t, x1multx2, z3); |
| // t = x1 * x2 * z3 - z3 |
| Field25519.sub(t, t, z3); |
| // t2 = (x1 + x2) * x3 |
| Field25519.mult(t2, x1addx2, x3); |
| // t = x1 * x2 * z3 - z3 + (x1 + x2) * x3 |
| Field25519.sum(t, t, t2); |
| // t = (x1 * x2 * z3 - z3 + (x1 + x2) * x3)^2 |
| Field25519.square(t, t); |
| return Bytes.equal(Field25519.contract(lhs), Field25519.contract(t)); |
| } |
| } |