blob: fc34569a3edae8c5415cce33b501e6ba34a1adff [file]
/*
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ec.ed;
import sun.security.ec.point.AffinePoint;
import sun.security.ec.point.Point;
import sun.security.util.ArrayUtil;
import sun.security.util.math.IntegerFieldModuloP;
import sun.security.util.math.IntegerModuloP;
import sun.security.util.math.MutableIntegerModuloP;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.spec.EdDSAParameterSpec;
import java.security.spec.EdECPoint;
import java.util.Arrays;
import java.util.function.Function;
/*
* A class containing the operations of the EdDSA signature scheme. The
* parameters include an object that performs the elliptic curve point
* arithmetic, and EdDSAOperations uses this object to construct the signing
* and verification operations.
*/
public class EdDSAOperations {
private final EdDSAParameters params;
public EdDSAOperations(EdDSAParameters params)
throws NoSuchAlgorithmException {
this.params = params;
}
public EdDSAParameters getParameters() {
return params;
}
public byte[] generatePrivate(SecureRandom random) {
byte[] result = new byte[params.getKeyLength()];
random.nextBytes(result);
return result;
}
public EdECPoint computePublic(byte[] privateKey) {
byte[] privateKeyHash = params.digest(privateKey);
int byteLength = privateKeyHash.length / 2;
byte[] s = Arrays.copyOf(privateKeyHash, byteLength);
prune(s);
IntegerModuloP fieldS = params.getOrderField().getElement(s);
fieldS.asByteArray(s);
Point A = params.getEdOperations().basePointMultiply(s);
return asEdECPoint(A.asAffine());
}
private static EdECPoint asEdECPoint(AffinePoint p) {
return new EdECPoint(p.getX().asBigInteger().testBit(0),
p.getY().asBigInteger());
}
public byte[] sign(EdDSAParameterSpec sigParams, byte[] privateKey,
byte[] message) {
byte[] privateKeyHash = params.digest(privateKey);
int byteLength = privateKeyHash.length / 2;
byte[] s = Arrays.copyOf(privateKeyHash, byteLength);
prune(s);
IntegerModuloP sElem = params.getOrderField().getElement(s);
sElem.asByteArray(s);
Point A = params.getEdOperations().basePointMultiply(s);
byte[] prefix = Arrays.copyOfRange(privateKeyHash,
privateKeyHash.length / 2, privateKeyHash.length);
byte[] dom = params.dom(sigParams);
byte[] r = params.digest(dom, prefix, message);
// reduce r modulo the order
IntegerModuloP fieldR = params.getOrderField().getElement(r);
r = new byte[params.getKeyLength()];
fieldR.asByteArray(r);
Point R = params.getEdOperations().basePointMultiply(r);
byte[] encodedR = encode(byteLength, R);
byte[] encodedA = encode(byteLength, A);
byte[] k = params.digest(dom, encodedR, encodedA, message);
// S computation is in group-order field
IntegerFieldModuloP subField = params.getOrderField();
IntegerModuloP kElem = subField.getElement(k);
IntegerModuloP rElem = subField.getElement(r);
MutableIntegerModuloP S = kElem.mutable().setProduct(sElem);
S.setSum(rElem);
byte[] sArr = S.asByteArray(byteLength);
byte[] rArr = encode(byteLength, R);
byte[] result = new byte[byteLength * 2];
System.arraycopy(rArr, 0, result, 0, byteLength);
System.arraycopy(sArr, 0, result, byteLength, byteLength);
return result;
}
public boolean verify(EdDSAParameterSpec sigParams, AffinePoint affineA,
byte[] publicKey, byte[] message, byte[] signature)
throws SignatureException {
if (signature == null) {
throw new SignatureException("signature was null");
}
if (params.getKeyLength() * 2 != signature.length) {
throw new SignatureException("signature length invalid");
}
byte[] encR = Arrays.copyOf(signature, signature.length / 2);
byte[] encS = Arrays.copyOfRange(signature, signature.length / 2,
signature.length);
// reject s if it is too large
ArrayUtil.reverse(encS);
BigInteger bigS = new BigInteger(1, encS);
if (bigS.compareTo(params.getOrderField().getSize()) >= 0) {
throw new SignatureException("s is too large");
}
ArrayUtil.reverse(encS);
byte[] dom = params.dom(sigParams);
AffinePoint affineR = decodeAffinePoint(SignatureException::new, encR);
byte[] k = params.digest(dom, encR, publicKey, message);
// reduce k to improve performance of multiply
IntegerFieldModuloP subField = params.getOrderField();
IntegerModuloP kElem = subField.getElement(k);
k = kElem.asByteArray(k.length / 2);
Point pointR = params.getEdOperations().of(affineR);
Point pointA = params.getEdOperations().of(affineA);
EdECOperations edOps = params.getEdOperations();
Point lhs = edOps.basePointMultiply(encS);
Point rhs = edOps.setSum(edOps.setProduct(pointA.mutable(), k),
pointR.mutable());
return lhs.affineEquals(rhs);
}
public boolean verify(EdDSAParameterSpec sigParams, byte[] publicKey,
byte[] message, byte[] signature)
throws InvalidKeyException, SignatureException {
AffinePoint affineA = decodeAffinePoint(InvalidKeyException::new,
publicKey);
return verify(sigParams, affineA, publicKey, message, signature);
}
public
<T extends Throwable>
AffinePoint decodeAffinePoint(Function<String, T> exception, byte[] arr)
throws T {
if (arr.length != params.getKeyLength()) {
throw exception.apply("incorrect length");
}
arr = arr.clone();
int xLSB = (0xFF & arr[arr.length - 1]) >>> 7;
arr[arr.length - 1] &= 0x7F;
int yLength = (params.getBits() + 7) >> 3;
IntegerModuloP y =
params.getField().getElement(arr, 0, yLength, (byte) 0);
// reject non-canonical y values
ArrayUtil.reverse(arr);
BigInteger bigY = new BigInteger(1, arr);
if (bigY.compareTo(params.getField().getSize()) >= 0) {
throw exception.apply("y value is too large");
}
return params.getEdOperations().decodeAffinePoint(exception, xLSB, y);
}
public
<T extends Throwable>
AffinePoint decodeAffinePoint(Function<String, T> exception,
EdECPoint point)
throws T {
// reject non-canonical y values
if (point.getY().compareTo(params.getField().getSize()) >= 0) {
throw exception.apply("y value is too large");
}
int xLSB = point.isXOdd() ? 1 : 0;
IntegerModuloP y = params.getField().getElement(point.getY());
return params.getEdOperations().decodeAffinePoint(exception, xLSB, y);
}
/**
* Mask off the high order bits of an encoded integer in an array. The
* array is modified in place.
*
* @param arr an array containing an encoded integer
* @param bits the number of bits to keep
* @return the number, in range [0,8], of bits kept in the highest byte
*/
private static int maskHighOrder(byte[] arr, int bits) {
int lastByteIndex = arr.length - 1;
int bitsDiff = arr.length * 8 - bits;
int highBits = 8 - bitsDiff;
byte msbMaskOff = (byte) ((1 << highBits) - 1);
arr[lastByteIndex] &= msbMaskOff;
return highBits;
}
/**
* Prune an encoded scalar value by modifying it in place. The extra
* high-order bits are masked off, the highest valid bit it set, and the
* number is rounded down to a multiple of the co-factor.
*
* @param k an encoded scalar value
* @param bits the number of bits in the scalar
* @param logCofactor the base-2 logarithm of the co-factor
*/
private static void prune(byte[] k, int bits, int logCofactor) {
int lastByteIndex = k.length - 1;
// mask off unused high-order bits
int highBits = maskHighOrder(k, bits);
// set the highest bit
if (highBits == 0) {
k[lastByteIndex - 1] |= (byte) 0x80;
} else {
byte msbMaskOn = (byte) (1 << (highBits - 1));
k[lastByteIndex] |= msbMaskOn;
}
// round down to a multiple of the co-factor
byte lsbMaskOff = (byte) (0xFF << logCofactor);
k[0] &= lsbMaskOff;
}
void prune(byte[] arr) {
prune(arr, params.getBits(), params.getLogCofactor());
}
private static byte[] encode(int length, Point p) {
return encode(length, p.asAffine());
}
private static byte[] encode(int length, AffinePoint p) {
byte[] result = p.getY().asByteArray(length);
int xLSB = p.getX().asByteArray(1)[0] & 0x01;
result[result.length - 1] |= (byte) (xLSB << 7);
return result;
}
}