package de.persosim.simulator.crypto; import static org.globaltester.logging.BasicLogger.DEBUG; import static org.globaltester.logging.BasicLogger.ERROR; import static org.globaltester.logging.BasicLogger.TRACE; import static org.globaltester.logging.BasicLogger.log; import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECField; import java.security.spec.ECFieldFp; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; import org.globaltester.cryptoprovider.Crypto; import de.persosim.simulator.tlv.ConstructedTlvDataObject; import de.persosim.simulator.tlv.PrimitiveTlvDataObject; import de.persosim.simulator.tlv.TlvConstants; import de.persosim.simulator.tlv.TlvTag; import de.persosim.simulator.utils.HexString; import de.persosim.simulator.utils.Utils; /** * This class specifies the domain parameter sets to be used for ECDH key * agreements or within ECDH keys. * * @author slutters * */ public class DomainParameterSetEcdh implements DomainParameterSet, TlvConstants { public static final byte[] id_ecPublicKey = HexString.toByteArray("2A8648CE3D0201"); public static final byte[] id_primeField = HexString.toByteArray("2A8648CE3D0101"); protected ECParameterSpec ecParameterSpec; public DomainParameterSetEcdh() {} /** * Constructor for constructing a {@link DomainParameterSetEcdh} object * @param curve the elliptic curve * @param g the group generator * @param n the group order * @param h the co-factor */ public DomainParameterSetEcdh(EllipticCurve curve, ECPoint g, BigInteger n, int h) { ecParameterSpec = new ECParameterSpec(curve, g,n, h); } /** * Constructor for constructing a {@link DomainParameterSetEcdh} object from {@link ECParameterSpec} * @param ecParameterSpec the {@link ECParameterSpec} to import */ public DomainParameterSetEcdh(ECParameterSpec ecParameterSpec) { this.ecParameterSpec = ecParameterSpec; } @Override public BigInteger getPrime() { ECField ecField = ecParameterSpec.getCurve().getField(); if (ecField instanceof ECFieldFp) { return ((ECFieldFp) ecField).getP(); } else{ throw new IllegalArgumentException("ECDH domain parameter set's curve does not provide a prime finite field"); } } /** * This method checks whether the provided EC point is on the curve specified by the parameters within this object. * @param ecPoint the EC point to be checked * @return whether the provided EC Point is on the curve */ public boolean isOnCurve(ECPoint ecPoint) { BigInteger two = BigInteger.ONE.add(BigInteger.ONE); BigInteger three = two.add(BigInteger.ONE); BigInteger p = getPrime(); EllipticCurve ecCurve = ecParameterSpec.getCurve(); BigInteger a = ecCurve.getA(); BigInteger b = ecCurve.getB(); BigInteger x = ecPoint.getAffineX(); BigInteger y = ecPoint.getAffineY(); BigInteger leftSide = y.modPow(two, p); BigInteger rightSide = (((x.modPow(three, p)).add(a.multiply(x))).add(b)).mod(p); boolean result = leftSide.compareTo(rightSide) == 0; if(!result) { log(getClass(), "point not on curve - x: " + HexString.encode(x), DEBUG); log(getClass(), "point not on curve - y: " + HexString.encode(y), DEBUG); log(getClass(), "point not on curve - p: " + HexString.encode(p), DEBUG); log(getClass(), "point not on curve - " + HexString.encode(leftSide) + " != " + HexString.encode(rightSide), DEBUG); log(getClass(), "point not on curve - x: " + x, DEBUG); log(getClass(), "point not on curve - y: " + y, DEBUG); log(getClass(), "point not on curve - p: " + p, DEBUG); log(getClass(), "point not on curve - " + leftSide + " != " + rightSide, DEBUG); } return result; } /** * Computes reference length l used for Point-to-Octet-String Conversion according to ANSI X9.62 chapter 4.3.6. * @return reference length l */ public int getPublicPointReferenceLengthL() { return getPublicPointReferenceLengthL(getPrime()); } /** * Computes reference length l used for Point-to-Octet-String Conversion according to ANSI X9.62 chapter 4.3.6. * @param q the prime * @return reference length l */ public static int getPublicPointReferenceLengthL(BigInteger q) { int log = q.bitLength(); int result = ((Double) Math.ceil(log/8.0)).intValue(); return result; } /** * This method creates a {@link ECPrivateKeySpec} object for a private key based on the provided secret key parameter and the basic domain parameters of this object. * @param privateX the secret key parameter * @return a {@link ECPrivateKeySpec} object for a private key */ public ECPrivateKeySpec getPrivateKeySpec(BigInteger privateD) { return new ECPrivateKeySpec(privateD, ecParameterSpec); } /** * This method creates a {@link ECPublicKeySpec} object for a public key based on the provided public key parameter and the basic domain parameters of this object. * @param privateX the public key parameter * @return a {@link ECPublicKeySpec} object for a public key */ public ECPublicKeySpec getPublicKeySpec(ECPoint publicW) { return new ECPublicKeySpec(publicW, ecParameterSpec); } @Override public KeySpec[] updateKeySpec(KeyPair keyPair) { ECPublicKey ecPublicKey; ECPrivateKey ecPrivateKey; try { ecPublicKey = (ECPublicKey) keyPair.getPublic(); ecPrivateKey = (ECPrivateKey) keyPair.getPrivate(); } catch (ClassCastException e) { throw new IllegalArgumentException("key pair must be EC"); } EllipticCurve ec = ecParameterSpec.getCurve(); ECPoint gUnmapped = ecPrivateKey.getParams().getGenerator(); ECPoint gMapped = ecParameterSpec.getGenerator(); BigInteger ecFirstCoefficientA = ec.getA(); BigInteger ecSecondCoefficientB = ec.getB(); BigInteger ecFp = getPrime(); BigInteger gUnmappedX = gUnmapped.getAffineX(); BigInteger gUnmappedY = gUnmapped.getAffineY(); BigInteger gMappedX = gMapped.getAffineX(); BigInteger gMappedY = gMapped.getAffineY(); if(gMappedX.equals(gUnmappedX) && gMappedY.equals(gUnmappedY)) { throw new IllegalArgumentException("ECDH unmapped and mapped generator g of group G are identical!"); } BigInteger order = getOrder(); int coFactor = ecParameterSpec.getCofactor(); BigInteger piccPrivateKeyD = ecPrivateKey.getS(); ECPoint publicPointW = ecPublicKey.getW(); BigInteger publicPointWx = publicPointW.getAffineX(); BigInteger publicPointWy = publicPointW.getAffineY(); ECPoint publicPointWmapped = CryptoUtil.scalarPointMultiplication(ec, getOrder(), gMapped, piccPrivateKeyD); BigInteger publicPointWmappedX = publicPointWmapped.getAffineX(); BigInteger publicPointWmappedY = publicPointWmapped.getAffineY(); log(getClass(), "ECDH ephemeral private key d is : " + HexString.encode(piccPrivateKeyD), DEBUG); log(getClass(), "ECDH ephemeral public point w.x under unmapped g is: " + HexString.encode(publicPointWx), DEBUG); log(getClass(), "ECDH ephemeral public point w.y under unmapped g is: " + HexString.encode(publicPointWy), DEBUG); log(getClass(), "ECDH ephemeral public point w.x under mapped g is : " + HexString.encode(publicPointWmappedX), DEBUG); log(getClass(), "ECDH ephemeral public point w.y under mapped g is : " + HexString.encode(publicPointWmappedY), DEBUG); log(getClass(), "ECDH curve's first coefficient A is : " + HexString.encode(ecFirstCoefficientA), DEBUG); log(getClass(), "ECDH curve's second coefficient B is : " + HexString.encode(ecSecondCoefficientB), DEBUG); log(getClass(), "ECDH original generator g.x of group G is : " + HexString.encode(gUnmappedX), DEBUG); log(getClass(), "ECDH original generator g.y of group G is : " + HexString.encode(gUnmappedY), DEBUG); log(getClass(), "ECDH mapped generator g.x of group G is : " + HexString.encode(gMappedX), DEBUG); log(getClass(), "ECDH mapped generator g.y of group G is : " + HexString.encode(gMappedY), DEBUG); log(getClass(), "ECDH prime modulus p of group G is : " + HexString.encode(ecFp), DEBUG); log(getClass(), "ECDH order of group G is : " + HexString.encode(order), DEBUG); log(getClass(), "ECDH cofactor is : " + coFactor, DEBUG); KeySpec mappedPrivateKeySpec = getPrivateKeySpec(piccPrivateKeyD); KeySpec mappedPublicKeySpec = getPublicKeySpec(publicPointWmapped); return new KeySpec[]{mappedPrivateKeySpec, mappedPublicKeySpec}; } @Override public String getKeyAgreementAlgorithm() { return "ECDH"; } /** * This method reconstructs an {@link ECPoint} object from its basic encoding according to ANSI X9.62. * Currently only uncompressed encoding indicated by leading 0x04 byte is supported. * @param rawKeyPlain the point encoded as byte array * @return the {@link ECPoint} representation of the provided byte array encoding */ public static ECPoint reconstructPoint(byte[] rawKeyPlain) { if(rawKeyPlain == null) {throw new NullPointerException("raw key material must not be null");}; log(DomainParameterSetEcdh.class, "raw public key EC point byte array is: " + HexString.encode(rawKeyPlain), TRACE); if(rawKeyPlain.length % 2 != 1) {throw new IllegalArgumentException("encoded public key EC point must be of uneven byte length");}; byte firstKeyByte = rawKeyPlain[0]; switch (firstKeyByte){ case (byte) 0x01: throw new IllegalArgumentException("encoding 0x01 of public key point is currently not supported"); //IMPL ECPoint encoding case (byte) 0x02: throw new IllegalArgumentException("encoding 0x02 of public key point is currently not supported"); //IMPL ECPoint encoding case (byte) 0x03: throw new IllegalArgumentException("encoding 0x03 of public key point is currently not supported"); //IMPL ECPoint encoding case (byte) 0x04: log(DomainParameterSetEcdh.class, "leading byte 0x04 of public key point indicates uncompressed encoding", TRACE); break; default: throw new IllegalArgumentException("encoding indication of public key point is unknown (must be 0x01..04)"); } // the following lines only work for uncompressed point encoding! int lengthOfCoordinates = (rawKeyPlain.length - 1)/2; byte[] pointXplain = Arrays.copyOfRange(rawKeyPlain, 1, 1 + lengthOfCoordinates); byte[] pointYplain = Arrays.copyOfRange(rawKeyPlain, 1 + lengthOfCoordinates, rawKeyPlain.length); log(DomainParameterSetEcdh.class, "byte array x coordinate of public key EC point is: " + HexString.encode(pointXplain), TRACE); log(DomainParameterSetEcdh.class, "byte array y coordinate of public key EC point is: " + HexString.encode(pointYplain), TRACE); BigInteger publicPointX = new BigInteger(1, pointXplain); BigInteger publicPointY = new BigInteger(1, pointYplain); log(DomainParameterSetEcdh.class, "x coordinate of public key EC point is: " + publicPointX, TRACE); log(DomainParameterSetEcdh.class, "y coordinate of public key EC point is: " + publicPointY, TRACE); return new ECPoint(publicPointX, publicPointY); } @Override public ECPublicKey reconstructPublicKey(byte[] rawKeyPlain) { return reconstructPublicKey(rawKeyPlain, Crypto.getCryptoProvider()); } public ECPublicKey reconstructPublicKey(byte[] rawKeyPlain, String providerString) { Provider cryptoProvider = Security.getProvider(providerString); return reconstructPublicKey(rawKeyPlain, cryptoProvider); } /** * This method reconstructs an {@link ECPublicKey} from its public point raw encoding * @param rawKeyPlain the public point encoding * @param cryptoProvider the crypto provider to use * @return the reconstructed {@link ECPublicKey} */ public ECPublicKey reconstructPublicKey(byte[] rawKeyPlain, Provider cryptoProvider) { int l = getPublicPointReferenceLengthL(); log(getClass(), "reference length l is: " + l + " bytes", TRACE); int expectedRawKeyLength = (2*l) + 1; if(rawKeyPlain.length != expectedRawKeyLength) { throw new IllegalArgumentException("public key data length mismatches expected length of " + expectedRawKeyLength + " bytes according to domain parameters"); } ECPoint point = reconstructPoint(rawKeyPlain); return reconstructPublicKey(point, cryptoProvider); } /** * This method reconstructs an {@link ECPublicKey} from its public point * @param point the public point * @param cryptoProvider the crypto provider to use * @return the reconstructed {@link ECPublicKey} */ public ECPublicKey reconstructPublicKey(ECPoint point, Provider cryptoProvider) { boolean pointOnCurve = isOnCurve(point); if(pointOnCurve) { log(this.getClass(), "EC point is on curve", TRACE); } else{ log(this.getClass(), "EC point is NOT on curve", ERROR); throw new IllegalArgumentException("public key data does not represent a point on the used curve"); } KeySpec reconstructedPublicKeySpec = new ECPublicKeySpec(point, ecParameterSpec); PublicKey mappedPublicKey; try { KeyFactory keyFactory = KeyFactory.getInstance(getKeyAgreementAlgorithm(), cryptoProvider); mappedPublicKey = keyFactory.generatePublic(reconstructedPublicKeySpec); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("invalid key agreement algorithm"); } catch (InvalidKeySpecException e) { throw new IllegalArgumentException("invalid public ECDH key"); } return (ECPublicKey) mappedPublicKey; } public ECPrivateKey reconstructPrivateKey(BigInteger privateS) { KeySpec reconstructedPrivateKeySpec = new ECPrivateKeySpec(privateS, ecParameterSpec); PrivateKey privateKey; try { KeyFactory keyFactory = KeyFactory.getInstance(getKeyAgreementAlgorithm(), Crypto.getCryptoProvider()); privateKey = keyFactory.generatePrivate(reconstructedPrivateKeySpec); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("invalid key agreement algorithm"); } catch (InvalidKeySpecException e) { throw new IllegalArgumentException("invalid public ECDH key"); } return (ECPrivateKey) privateKey; } @Override public ECPrivateKey reconstructPrivateKey(byte[] rawKeyPlain) { BigInteger privateS = new BigInteger(1, rawKeyPlain); if(privateS.compareTo(this.getPrime()) >= 0) { throw new IllegalArgumentException("private key is greater than or equal to prime"); } return reconstructPrivateKey(privateS); } /** * This method returns an encoding of the provided public key according to the also provided encoding. * See {@link CryptoUtil#encode(ECPoint, int, byte)}} for the supported encodings * @param publicKey the public key to encode * @param encoding the encoding * @return the public key encoding */ public byte[] encodePublicKey(PublicKey publicKey, byte encoding) { ECPublicKey ecPublicKey; if(publicKey instanceof ECPublicKey) { ecPublicKey = (ECPublicKey) publicKey; } else{ throw new IllegalArgumentException("invalid public ECDH key"); } return encodePoint(ecPublicKey.getW(), encoding); } @Override public byte[] encodePrivateKey(PrivateKey privateKey) { if(privateKey instanceof ECPrivateKey) { return Utils.toUnsignedByteArray(((ECPrivateKey) privateKey).getS()); } else{ throw new IllegalArgumentException("key pair must be ECDH"); } } @Override public byte[] encodePublicKey(PublicKey publicKey) { // This method returns an EC point encoding according to ANSI X9.62 uncompressed mode return encodePublicKey(publicKey, CryptoUtil.ENCODING_UNCOMPRESSED); } /** * This method returns an encoding of a provided {@link ECPoint} according to the domain parameters specified by this object. * See {@link CryptoUtil#encode(ECPoint, int, byte)} for the supported encodings. * @param ecPoint the point to be encoded * @param encoding the encoding to be used * @return an encoding of a provided {@link ECPoint} according to the domain parameters specified by this object */ public byte[] encodePoint(ECPoint ecPoint, byte encoding) { /* * This check is necessary to ensure that shorter point encodings from * another ec domain parameter set will also be rejected and not * processed unnoticed. */ if(!isOnCurve(ecPoint)) { return null; } int publicPointReferenceLength = getPublicPointReferenceLengthL(getPrime()); return CryptoUtil.encode(ecPoint, publicPointReferenceLength, encoding); } @Override public TlvTag getAuthenticationTokenPublicKeyTag() { return TAG_86; } /** * This method returns a copy of this object that has been mapped according to the provided generator. * @param gMapped the new generator to be used * @return a copy of this object that has been mapped according to the provided generator */ public DomainParameterSetEcdh getUpdatedDomainParameterSet(ECPoint gMapped) { return new DomainParameterSetEcdh(ecParameterSpec.getCurve(), gMapped, getOrder(), ecParameterSpec.getCofactor()); } @Override public byte[] comp(PublicKey publicKey) { ECPublicKey ecPublicKey; if(publicKey instanceof ECPublicKey) { ecPublicKey = (ECPublicKey) publicKey; } else{ throw new IllegalArgumentException("public key must be an EC public key"); } ECPoint publicPoint = ecPublicKey.getW(); BigInteger publicPointX = publicPoint.getAffineX(); ECField field = ecPublicKey.getParams().getCurve().getField(); if(field instanceof ECFieldFp){ ECFieldFp fieldFp = (ECFieldFp) field; int expectedLength = (int) Math.ceil(Utils.logarithm(fieldFp.getP().doubleValue(), 256)); byte [] result = Utils.toUnsignedByteArray(publicPointX); if (result.length < expectedLength){ byte [] padding = new byte [expectedLength - result.length]; result = Utils.concatByteArrays(padding, result); } return result; } return null; } @Override public BigInteger getOrder() { return ecParameterSpec.getOrder(); } /** * This method returns the cofactor. * @return the cofactor */ public int getCofactor() { return ecParameterSpec.getCofactor(); } /** * This method returns the elliptic curve that this parameter defines. * @return the elliptic curve that this parameter defines */ public EllipticCurve getCurve() { return ecParameterSpec.getCurve(); } /** * This method returns the generator which is also known as the base point. * @return the generator which is also known as the base point */ public ECPoint getGenerator() { return ecParameterSpec.getGenerator(); } @Override public ECParameterSpec getKeySpec() { return ecParameterSpec; } @Override public ConstructedTlvDataObject getAlgorithmIdentifier() { PrimitiveTlvDataObject version = new PrimitiveTlvDataObject(TlvConstants.TAG_INTEGER, new byte[] {0x01}); ConstructedTlvDataObject fieldId = new ConstructedTlvDataObject(TlvConstants.TAG_SEQUENCE); fieldId.addTlvDataObject(new PrimitiveTlvDataObject(TlvConstants.TAG_OID, id_primeField)); fieldId.addTlvDataObject(new PrimitiveTlvDataObject(TlvConstants.TAG_INTEGER, getPrime().toByteArray())); ConstructedTlvDataObject curve = new ConstructedTlvDataObject(TlvConstants.TAG_SEQUENCE); curve.addTlvDataObject(new PrimitiveTlvDataObject(TlvConstants.TAG_OCTET_STRING, getCurve().getA().toByteArray())); curve.addTlvDataObject(new PrimitiveTlvDataObject(TlvConstants.TAG_OCTET_STRING, getCurve().getB().toByteArray())); PrimitiveTlvDataObject base = new PrimitiveTlvDataObject(TlvConstants.TAG_OCTET_STRING, CryptoUtil.encode(getGenerator(), getPublicPointReferenceLengthL(getPrime()), CryptoUtil.ENCODING_UNCOMPRESSED)); PrimitiveTlvDataObject order = new PrimitiveTlvDataObject(TlvConstants.TAG_INTEGER, getOrder().toByteArray()); PrimitiveTlvDataObject cofactor = new PrimitiveTlvDataObject(TlvConstants.TAG_INTEGER, BigInteger.valueOf(getCofactor()).toByteArray()); ConstructedTlvDataObject params = new ConstructedTlvDataObject(TlvConstants.TAG_SEQUENCE); params.addTlvDataObject(version); params.addTlvDataObject(fieldId); params.addTlvDataObject(curve); params.addTlvDataObject(base); params.addTlvDataObject(order); params.addTlvDataObject(cofactor); ConstructedTlvDataObject retVal = new ConstructedTlvDataObject(TlvConstants.TAG_SEQUENCE); retVal.addTlvDataObject(new PrimitiveTlvDataObject(TlvConstants.TAG_OID, id_ecPublicKey)); retVal.addTlvDataObject(params); return retVal; } @Override public int hashCode() { //implement hashCode() and equals based on the byte[] representation of getAlgorithmIdentifier return getAlgorithmIdentifier().hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } DomainParameterSetEcdh other = (DomainParameterSetEcdh) obj; return getAlgorithmIdentifier().equals(other.getAlgorithmIdentifier()); } @Override public ECParameterSpec getAlgorithmParameterSpec() { return ecParameterSpec; } @Override public String toString() { StringBuffer sb = new StringBuffer(); EllipticCurve curve = getCurve(); BigInteger a = curve.getA(); BigInteger b = curve.getB(); BigInteger p = getPrime(); BigInteger order = getOrder(); int coFactor = getCofactor(); ECPoint generator = getGenerator(); int referenceLength = DomainParameterSetEcdh.getPublicPointReferenceLengthL(p); sb.append("************ elliptic curve domain parameters ************"); sb.append("\nCurve parameter A : " + HexString.encode(a)); sb.append("\nCurve parameter B : " + HexString.encode(b)); sb.append("\nPrime field p : " + HexString.encode(p)); sb.append("\nOrder of generator: " + HexString.encode(order)); sb.append("\nCo-factor h : " + coFactor); sb.append("\nGenerator G : " + HexString.encode(CryptoUtil.encode(generator, referenceLength, CryptoUtil.ENCODING_HYBRID))); sb.append("\nGenerator G.x : " + " " + HexString.encode(generator.getAffineX())); char[] array = new char[(2*referenceLength) + 2]; Arrays.fill(array, ' '); String padding = new String(array); sb.append("\nGenerator G.y : " + padding + HexString.encode(generator.getAffineY())); sb.append("\n**********************************************************"); return sb.toString(); } /** * This method performs a manual ECDH key agreement that returns a complete * EC point as its result. The key agreement provided by Bouncy Castle via * the Java crypto API only returns the x-coordinate of the expected EC * point and reconstructing the y-coordinate is complicated by the ambiguity * of the coordinate. * * @param domainParameterSetEcdh * the domain parameters to be used * @param ecPublicKey * the public key to use * @param ecPrivateKey * the private key to use * @return */ public ECPoint performEcdhKeyAgreement(ECPublicKey ecPublicKey, ECPrivateKey ecPrivateKey) { ECPoint secretPoint = CryptoUtil.scalarPointMultiplication(getCurve(), getOrder(), ecPublicKey.getW(), ecPrivateKey.getS()); log(CryptoUtil.class, "result H of ECDH key agreement is", TRACE); log(CryptoUtil.class, "H.x: " + HexString.encode(secretPoint.getAffineX()), TRACE); log(CryptoUtil.class, "H.y: " + HexString.encode(secretPoint.getAffineY()), TRACE); return secretPoint; } /** * This method returns a projection of the provided point's selected coordinate. * If the length of the selected encoded coordinate is less than the provided reference length, it is padded to this length. * @param ecPoint the point to work on * @param encodeX true: encode x-coordinate, false: encode y-coordinate * @return the projection of the provided point's selected coordinate */ public byte[] getProjectedRepresentation(ECPoint ecPoint, boolean encodeX) { return CryptoUtil.getProjectedRepresentation(ecPoint, getPublicPointReferenceLengthL(), encodeX); } /** * This method returns a projection of the provided point's x-coordinate. * If the length of the encoded x-coordinate is less than the provided reference length, it is padded to this length. * @param ecPoint the point to work on * @return the projection of the provided point's x-coordinate */ public byte[] getProjectedRepresentation(ECPoint ecPoint) { return CryptoUtil.getProjectedRepresentation(ecPoint, getPublicPointReferenceLengthL(), true); } /** * This method returns a public {@link ECPoint} matching the provided private {@link BigInteger} s * @param privateS the private component * @return the public component */ public ECPoint computePublicPoint(BigInteger privateS) { return CryptoUtil.scalarPointMultiplication(getCurve(), getGenerator(), privateS); } /** * This method returns the {@link ECPublicKey} matching the provided {@link ECPrivateKey}. * @param ecPrivateKey the private key to use * @param cryptoProvider the crypto provider to use * @return the public key matching the provided private key */ public ECPublicKey computePublicKey(ECPrivateKey ecPrivateKey, Provider cryptoProvider) { ECPoint publicPoint = computePublicPoint(ecPrivateKey.getS()); return reconstructPublicKey(publicPoint, cryptoProvider); } }