Я пытаюсь реализовать ECDSA, используя кривую secp256k1 и детерминированную генерацию K с помощью RFC6979 для подписывания транзакций, предназначенных для цепочки блоков steem в Java. Я не мастер криптографии, но, как я понимаю, в Bouncy Castle 1.5 есть все, что мне нужно.
Для контекста я использую openjdk 1.8.0_191 на linux mint 18.2. IDE для этого проекта - Intellij 2018.3.5 Community Edition. Я также использую javafx для создания GUI для моего приложения.
Я отправляю HTTPS-запросы на удаленный узел steemd через JSON RPC. Получение данных с узла не составило никаких проблем, но у меня возникли проблемы с созданием действительных подписей для транзакций операции широковещания, в данном случае операции голосования. Подпись, по-видимому, действительна в форме, но декодируется с неверным открытым ключом, поэтому моя транзакция не принимается узлом.
Я следую руководству, написанному на python, его можно найти здесь , а полный исходный код здесь . Гид, как 3 года, хотя. Я не уверен, что с тех пор изменилось (если вообще что-то) в этом процессе, и у меня возникли проблемы с поиском документации для steem.
Вот соответствующий код из моего класса Controller,
Digest sha256 = new SHA256Digest();//BouncyCastle SHA256 object
sha256.update(ser, 0, ser.length);//Ser is the serialized transaction, I'm fairly confident I serialize correctly
byte[] digest = new byte[32];//this byte[] holds the hash of ser
sha256.doFinal(digest, 0);
String wif = pwdPost.getText().trim();//Gets the private key, represented as wif string, from javafx password textbox
byte[] keybytes = Utils.wif2key(wif);//Utils is my own static class of helper functions, I'm fairly certain wif2key is correct too
byte[][] signout = Utils.signTransaction(digest , keybytes, sha256);//signTransaction is where I think the error is
byte[] r, s;//The rest is just putting the signature data into a string for the payload
r = Arrays.copyOfRange(signout[0], 0, signout[0].length);
s = Arrays.copyOfRange(signout[1], 0, signout[1].length);
byte[] sigbytes = new byte[r.length + s.length + 1];
sigbytes[0] = signout[2][0];
for (int i = 0; i < r.length; i++){
sigbytes[i+1] = r[i];
}
for(int i = 0; i < s.length; i++){
sigbytes[i+r.length+1] = s[i];
}
String sig = DatatypeConverter.printHexBinary(sigbytes);
Вот функция, в которой я пытаюсь подписать данные,
public static byte[][] signTransaction(byte[] data, byte[] privateKey, Digest d) {
try {
byte[] r;
byte[] s;
BigInteger[] sig;
LinkedList<byte[]> sigout = new LinkedList<byte[]>();
Security.addProvider(new BouncyCastleProvider());
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
do {
HMacDSAKCalculator kmaker = new HMacDSAKCalculator(d);
ECDSASigner ecdsaSigner = new ECDSASigner(kmaker);
ECDomainParameters domain = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN());
ECPrivateKeyParameters privateKeyParms = new ECPrivateKeyParameters(new BigInteger(1, privateKey), domain);
ParametersWithRandom params = new ParametersWithRandom(privateKeyParms);
ecdsaSigner.init(true, params);
sig = ecdsaSigner.generateSignature(data);
r = sig[0].toByteArray();
s = sig[1].toByteArray();
}while(r.length != 32 || s.length != 32);
byte[] publicKey = getPublicKey(privateKey);
byte recoveryId = getRecoveryId(r, s, data, publicKey);
for (BigInteger sigChunk : sig) {
sigout.add(sigChunk.toByteArray());
}
sigout.add(new byte[]{(byte)(recoveryId + 31)});
return sigout.toArray(new byte[][]{});
} catch (Exception e) {
StringWriter errors = new StringWriter();
e.printStackTrace(new PrintWriter(errors));
return new byte[0][0];
}
}
Эта функция signTransaction вызывает две другие функции в моем коде. Для полноты я включу их на всякий случай, но я не думаю, что проблема заключается в этом.
public static byte[] getPublicKey(byte[] privateKey) {
try {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint pointQ = spec.getG().multiply(new BigInteger(1, privateKey));
return pointQ.getEncoded(false);
} catch (Exception e) {
StringWriter errors = new StringWriter();
e.printStackTrace(new PrintWriter(errors));
return new byte[0];
}
}
public static byte getRecoveryId(byte[] sigR, byte[] sigS, byte[] message, byte[] publicKey) {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
BigInteger pointN = spec.getN();
for (int recoveryId = 0; recoveryId < 2; recoveryId++) {
try {
BigInteger pointX = new BigInteger(1, sigR);
X9IntegerConverter x9 = new X9IntegerConverter();
byte[] compEnc = x9.integerToBytes(pointX, 1 + x9.getByteLength(spec.getCurve()));
compEnc[0] = (byte) ((recoveryId & 1) == 1 ? 0x03 : 0x02);
ECPoint pointR = spec.getCurve().decodePoint(compEnc);
if (!pointR.multiply(pointN).isInfinity()) {
continue;
}
BigInteger pointE = new BigInteger(1, message);
BigInteger pointEInv = BigInteger.ZERO.subtract(pointE).mod(pointN);
BigInteger pointRInv = new BigInteger(1, sigR).modInverse(pointN);
BigInteger srInv = pointRInv.multiply(new BigInteger(1, sigS)).mod(pointN);
BigInteger pointEInvRInv = pointRInv.multiply(pointEInv).mod(pointN);
ECPoint pointQ = ECAlgorithms.sumOfTwoMultiplies(spec.getG(), pointEInvRInv, pointR, srInv);
byte[] pointQBytes = pointQ.getEncoded(false);
boolean matchedKeys = true;
for (int j = 0; j < publicKey.length; j++) {
if (pointQBytes[j] != publicKey[j]) {
matchedKeys = false;
break;
}
}
if (!matchedKeys) {
continue;
}
return (byte) (0xFF & recoveryId);
} catch (Exception e) {
StringWriter errors = new StringWriter();
e.printStackTrace(new PrintWriter(errors));
//logger.error(errors.toString());
continue;
}
}
return (byte) 0xFF;
}