Надувной замок ECDSA генерирует неправильные подписи - PullRequest
1 голос
/ 05 марта 2019

Я пытаюсь реализовать 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;
}
...