This is an improvement to the answer from @SkateScout. I used the scalar routines in another context, published them here on SO (Derive EC public key from private key string in native Java for curve secp256k1) and got an "error" comment from @President James Moveon Polk:
"That code is incorrect, as is the code you got it from. This line BigInteger k = kin.mod(p); is wrong, wrong, wrong. The scalar multiple should be taken mod the order of the group, which is not the prime over which the field is defined. The only reason you haven't been bitten by that bug is because you never tested it with a kin that was >= p. That's why you don't do crypto by hand. But anyway, the answer to your question is no. If you were exclusively using Java 8 (or maybe 7) you could find an internal sun. class and method that does scalar multiplication, but not beyond that.* "
I changed the source code a little and this is the final code (complete example):
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.*;
public class DerivePublicKeyFromPrivateKeyCurveSecp256k1Final {
// sourcecode available https://github.com/java-crypto/Stackoverflow/tree/master/DervicePublicKeyFromPrivateKeyCurveSecp256k1
// get bouncycastle here: https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on/1.65
// tested with version 15 1.65
// java open jdk 11.0.5
final static BigInteger FieldP_2 = BigInteger.TWO; // constant for scalar operations
final static BigInteger FieldP_3 = BigInteger.valueOf(3); // constant for scalar operations
public static void main(String[] args) throws GeneralSecurityException {
System.out.println("Generate ECPublicKey from PrivateKey (String) for curve secp256k1 (final)");
System.out.println("Check keys with https://gobittest.appspot.com/Address");
// https://gobittest.appspot.com/Address
String privateKey = "D12D2FACA9AD92828D89683778CB8DFCCDBD6C9E92F6AB7D6065E8AACC1FF6D6";
String publicKeyExpected = "04661BA57FED0D115222E30FE7E9509325EE30E7E284D3641E6FB5E67368C2DB185ADA8EFC5DC43AF6BF474A41ED6237573DC4ED693D49102C42FFC88510500799";
System.out.println("\nprivatekey given : " + privateKey);
System.out.println("publicKeyExpected: " + publicKeyExpected);
// routine with bouncy castle
System.out.println("\nGenerate PublicKey from PrivateKey with BouncyCastle");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); // this ec curve is used for bitcoin operations
org.bouncycastle.math.ec.ECPoint pointQ = spec.getG().multiply(new BigInteger(1, hexStringToByteArray(privateKey)));
byte[] publickKeyByte = pointQ.getEncoded(false);
String publicKeyBc = byteArrayToHexString(publickKeyByte);
System.out.println("publicKeyExpected: " + publicKeyExpected);
System.out.println("publicKey BC : " + publicKeyBc);
System.out.println("publicKeys match : " + publicKeyBc.contentEquals(publicKeyExpected));
// regeneration of ECPublicKey with java native starts here
System.out.println("\nGenerate PublicKey from PrivateKey with Java native routines");
// the preset "303E.." only works for elliptic curve secp256k1
// see answer by user dave_thompson_085
// https://mcmap.net/q/2029845/-generate-ec-public-key-from-byte-array-private-key-in-native-java-7
String privateKeyFull = "303E020100301006072A8648CE3D020106052B8104000A042730250201010420" +
privateKey;
byte[] privateKeyFullByte = hexStringToByteArray(privateKeyFull);
System.out.println("privateKey full : " + privateKeyFull);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKeyNative = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFullByte));
ECPrivateKey ecPrivateKeyNative = (ECPrivateKey) privateKeyNative;
ECPublicKey ecPublicKeyNative = getPublicKey(ecPrivateKeyNative);
byte[] ecPublicKeyNativeByte = ecPublicKeyNative.getEncoded();
String publicKeyNativeFull = byteArrayToHexString(ecPublicKeyNativeByte);
String publicKeyNativeHeader = publicKeyNativeFull.substring(0, 46);
String publicKeyNativeKey = publicKeyNativeFull.substring(46, 176);
System.out.println("ecPublicKeyFull : " + publicKeyNativeFull);
System.out.println("ecPublicKeyHeader: " + publicKeyNativeHeader);
System.out.println("ecPublicKeyKey : " + publicKeyNativeKey);
System.out.println("publicKeyExpected: " + publicKeyExpected);
System.out.println("publicKeys match : " + publicKeyNativeKey.contentEquals(publicKeyExpected));
}
private static String byteArrayToHexString(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for (byte b : a)
sb.append(String.format("%02X", b));
return sb.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
// scalar operations for native java
// see https://mcmap.net/q/1935487/-codes-to-generate-a-public-key-in-an-elliptic-curve-algorithm-using-a-given-private-key
// written by author: SkateScout
private static ECPoint doublePoint(final BigInteger p, final BigInteger a, final ECPoint R) {
if (R.equals(ECPoint.POINT_INFINITY)) return R;
BigInteger slope = (R.getAffineX().pow(2)).multiply(FieldP_3);
slope = slope.add(a);
slope = slope.multiply((R.getAffineY().multiply(FieldP_2)).modInverse(p));
final BigInteger Xout = slope.pow(2).subtract(R.getAffineX().multiply(FieldP_2)).mod(p);
final BigInteger Yout = (R.getAffineY().negate()).add(slope.multiply(R.getAffineX().subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
private static ECPoint addPoint(final BigInteger p, final BigInteger a, final ECPoint r, final ECPoint g) {
if (r.equals(ECPoint.POINT_INFINITY)) return g;
if (g.equals(ECPoint.POINT_INFINITY)) return r;
if (r == g || r.equals(g)) return doublePoint(p, a, r);
final BigInteger gX = g.getAffineX();
final BigInteger sY = g.getAffineY();
final BigInteger rX = r.getAffineX();
final BigInteger rY = r.getAffineY();
final BigInteger slope = (rY.subtract(sY)).multiply(rX.subtract(gX).modInverse(p)).mod(p);
final BigInteger Xout = (slope.modPow(FieldP_2, p).subtract(rX)).subtract(gX).mod(p);
BigInteger Yout = sY.negate().mod(p);
Yout = Yout.add(slope.multiply(gX.subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
public static ECPoint scalmultNew(final ECParameterSpec params, final ECPoint g, final BigInteger kin) {
EllipticCurve curve = params.getCurve();
final ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) throw new UnsupportedOperationException(field.getClass().getCanonicalName());
final BigInteger p = ((ECFieldFp) field).getP();
final BigInteger a = curve.getA();
ECPoint R = ECPoint.POINT_INFINITY;
// value only valid for curve secp256k1, code taken from https://www.secg.org/sec2-v2.pdf,
// see "Finally the order n of G and the cofactor are: n = "FF.."
BigInteger SECP256K1_Q = params.getOrder();
//BigInteger SECP256K1_Q = new BigInteger("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16);
BigInteger k = kin.mod(SECP256K1_Q); // uses this !
// BigInteger k = kin.mod(p); // do not use this ! wrong as per comment from President James Moveon Polk
final int length = k.bitLength();
final byte[] binarray = new byte[length];
for (int i = 0; i <= length - 1; i++) {
binarray[i] = k.mod(FieldP_2).byteValue();
k = k.shiftRight(1);
}
for (int i = length - 1; i >= 0; i--) {
R = doublePoint(p, a, R);
if (binarray[i] == 1) R = addPoint(p, a, R, g);
}
return R;
}
public static ECPoint scalmultOrg(final EllipticCurve curve, final ECPoint g, final BigInteger kin) {
final ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) throw new UnsupportedOperationException(field.getClass().getCanonicalName());
final BigInteger p = ((ECFieldFp) field).getP();
final BigInteger a = curve.getA();
ECPoint R = ECPoint.POINT_INFINITY;
// value only valid for curve secp256k1, code taken from https://www.secg.org/sec2-v2.pdf,
// see "Finally the order n of G and the cofactor are: n = "FF.."
BigInteger SECP256K1_Q = new BigInteger("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16);
BigInteger k = kin.mod(SECP256K1_Q); // uses this !
// wrong as per comment from President James Moveon Polk
// BigInteger k = kin.mod(p); // do not use this !
System.out.println(" SECP256K1_Q: " + SECP256K1_Q);
System.out.println(" p: " + p);
System.out.println("curve: " + curve.toString());
final int length = k.bitLength();
final byte[] binarray = new byte[length];
for (int i = 0; i <= length - 1; i++) {
binarray[i] = k.mod(FieldP_2).byteValue();
k = k.shiftRight(1);
}
for (int i = length - 1; i >= 0; i--) {
R = doublePoint(p, a, R);
if (binarray[i] == 1) R = addPoint(p, a, R, g);
}
return R;
}
public static ECPublicKey getPublicKey(final ECPrivateKey pk) throws GeneralSecurityException {
final ECParameterSpec params = pk.getParams();
final ECPoint w = scalmultNew(params, pk.getParams().getGenerator(), pk.getS());
//final ECPoint w = scalmult(params.getCurve(), pk.getParams().getGenerator(), pk.getS());
final KeyFactory kg = KeyFactory.getInstance("EC");
return (ECPublicKey) kg.generatePublic(new ECPublicKeySpec(w, params));
}
}
Enjoy!
k
you have to calculatekG
whereG
is the generator of the used group. – Jorrie