0

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.

1 Answer 1

1

Your signatures don't verify because in JCE you are using algorithm SHA256withECDSA which hashes the supplied data (re-hashes, since you are supplying a hash) before doing the kG.x,kinv(h+rd) computation, or the similar verification computation, whereas the spongy/bouncy ECDSASigner used in bitcoinj only does the latter, assuming the input is already the correct hash. If you use JCE NONEwithECDSA instead, it will verify okay.

Note however the correct hash for bitcoin transaction signing is double SHA256, not the single SHA256 you coded. Although the data you are SHA256'ing isn't a bitcoin transaction at all, so the fact you are hashing it wrong is kind of moot.

Finally, even with these fixed, the signatures will still be different. JCE using the SunEC provider uses random K as per the standard (X9.62 and FIPS 186-2), whereas BC LWAPI can and the bitcoinj code you copied does use 'synthetic' K per RFC6979. Both are valid, but the signature values are totally different. (The BC provider does have some 'ECDDSA' (D for deterministic) algorithms using this K calculation, but only with hashing, and anyway if you have BC provider you also have BC LWAPI.)

In addition, as you seem to have detected, bitcoin uses 'low S' signatures; neither JCE nor BC LWAPI does that for you, so in either case you must add it (as bitcoinj does in toCanonicalised).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.