I need to sign Bitcoin transactions using JCE primitives(no bitcoinj), but my signatures are not considered valid by bitcoinj.
I tried to simulate this process. I created a random hash and signed it using both JCE and bitcoinj. Signatures are not equal.
Here is the code
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import sun.security.ec.ECPrivateKeyImpl;
import sun.security.ec.ECPublicKeyImpl;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
public class ECDSABitcoin {
private static final String SIGN_ALGORITHM = "SHA256withECDSA";
public static void main(String[] args) throws Exception {
KeyPairGenerator keyPairGenerator = createGenerator();
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPrivateKeyImpl privateKey = (ECPrivateKeyImpl) keyPair.getPrivate();
ECPublicKeyImpl publicKey = (ECPublicKeyImpl) keyPair.getPublic();
try {
Sha256Hash hashOut = Sha256Hash.wrap(toSha256("abc".getBytes()));
byte[] signatureBytes = sign(hashOut.getBytes(), keyPair);
ECKey.ECDSASignature mySignature = ECKey.ECDSASignature.decodeFromDER(signatureBytes).toCanonicalised();
ECKey.ECDSASignature bitcoinSignature = sign(privateKey.getS(), hashOut.getBytes()).toCanonicalised();
System.out.println("My signature s " + mySignature.s + " r " + mySignature.r + " canonical " + mySignature.isCanonical());
System.out.println("Verify my " + verify(keyPair, hashOut.getBytes(), signatureBytes));
System.out.println("Bitcoinj signature s " + bitcoinSignature.s + " r " + bitcoinSignature.r + " canonical " + bitcoinSignature.isCanonical());
System.out.println("Verify Bitcoinj " + verify(keyPair, hashOut.getBytes(), bitcoinSignature.encodeToDER()));
} catch (Exception e) {
e.printStackTrace();
}
}
private static boolean verify(KeyPair keyPair, byte[] message, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature verifySignature = Signature.getInstance(SIGN_ALGORITHM);
verifySignature.initVerify(keyPair.getPublic());
verifySignature.update(message);
return verifySignature.verify(signatureBytes);
}
private static byte[] sign(byte[] message, KeyPair keyPair) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
final Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(keyPair.getPrivate());
signature.update(message);
return signature.sign();
}
private static KeyPairGenerator createGenerator() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecParam = new ECGenParameterSpec("secp256k1");
keyPairGenerator.initialize(ecParam);
return keyPairGenerator;
}
private static ECKey.ECDSASignature sign(BigInteger privateKeyForSigning, byte[] data) {
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, ECKey.CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(data);
return new ECKey.ECDSASignature(components[0], components[1]).toCanonicalised();
}
private static byte[] toSha256(byte[] message) throws NoSuchAlgorithmException {
MessageDigest crypt = MessageDigest.getInstance("SHA-256");
crypt.reset();
crypt.update(message);
return crypt.digest();
}
}
The result is:
My signature s 45669553786690215047884329722902825758089042579493437816717142987836102849876 r 14778973653615637448416336446742229796258878351047437829727432860950944374049 canonical true
Verify my true
Bitcoinj signature s 24278043061766196831119988370534304503511938256487950554838614741011144316017 r 26413727078831382349368962255251267289169651926313668837949728205557969096319 canonical true
Verify Bitcoinj false
As you see, signatures are completely different, even though I use the same private key. What is wrong with my code? I just don't get it.