我正在尝试使用曲线secp256k1和RFC6979的确定性K生成来实现ECDSA,以对Java中的Steem区块链进行交易签名。我不是加密向导,但据我了解,Bouncy Castle 1.5具有我需要的一切。
对于上下文,我在Linux Mint 18.2上使用openjdk 1.8.0_191。该项目的IDE是Intellij 2018.3.5社区版。我还使用javafx为我的应用程序制作GUI。
我正在通过JSON RPC将HTTPS请求发送到远程Steemd节点。从节点获取数据根本没有问题,但是我很难为广播操作事务(在这种情况下为表决操作)生成有效签名。签名在形式上显然有效,但是会解码为错误的公钥,因此节点不接受我的交易。
我正在遵循用python编写的指南,可以在here中找到它,使用的完整源代码是here。该指南虽然大约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;
}