I want to verify, in Java, a signature produced with bitcoin core's signmessagewithprivkey (equivalent to the verifymessage). I noticed a bitcoinj method similar to the bitcoin core's verifymessage - org.bitcoinj.core.ECKey.verify. What follows is my implementation attempt and its test.

For testing I will generate a signature and check it in bitcoin core:

>bitcoin-cli signmessagewithprivkey $(bitcoin-cli dumpprivkey 1CwKH9PQPkFPjQagEv483FUM5ngk57L3Pp) ""
> bitcoin-cli verifymessage "1CwKH9PQPkFPjQagEv483FUM5ngk57L3Pp" "H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=" ""

So, signing an empty message with the RPC using the address 1CwKH9PQPkFPjQagEv483FUM5ngk57L3Pp (public key: 021c3be5fb7820c56d881ea2d02a906d87540ec8888bbe819b7abd2e39f6f6e512), produces the signature H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=.

My Java implementation attempt:

import org.apache.commons.codec.binary.Hex;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import java.math.BigInteger;
import java.util.Base64;

public class Notebook {

    public static BigInteger[] ParseSig(byte[] sigBytes, int sigOff) {

        BigInteger r = new BigInteger( 1 ,sigBytes, sigOff, 32);
        BigInteger s = new BigInteger( 1, sigBytes, sigOff + 32, 32);
        return new BigInteger[] { r, s };

    public static void main(String[] args) throws Exception{

        // Signature
        String signatureString = "H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=";
        byte[] signatureBytes = Base64.getDecoder().decode(signatureString);
        BigInteger[] signatureRandS = ParseSig(signatureBytes, 1);
        ECKey.ECDSASignature signature = new ECKey.ECDSASignature(signatureRandS[0], signatureRandS[1]);

        // Public key
        String pubKeyString = "021c3be5fb7820c56d881ea2d02a906d87540ec8888bbe819b7abd2e39f6f6e512";
        byte[] decodedPubKeyString = Hex.decodeHex(pubKeyString.toCharArray());
        ECKey publicKey = ECKey.fromPublicOnly(decodedPubKeyString);

        // Message
        String message = "Bitcoin Signed Message:\n";
        Sha256Hash messageHash = Sha256Hash.of(message.getBytes());
        Sha256Hash messageDoubleHash = Sha256Hash.of(messageHash.getBytes());

        // Test
        boolean result = publicKey.verify(messageDoubleHash, signature);
        System.out.println("Result: " + result);

Notice that I used the salt Bitcoin Signed Message:\n before hashing the message - this is done by signmessagewithprivkey as shown here and here. The problem is the above implementation doesn't pass my test and I don't know why...


Thanks to Septem151 I was able to fix this. The code passes the test, if the message section is replaced with:

        // Message
        String message = "Bitcoin Signed Message:\n";
        byte[] messageBytesEncoded = new byte[message.getBytes().length + 2];
        messageBytesEncoded[0] = 0x18;
        messageBytesEncoded[messageBytesEncoded.length - 1] = 0x00;
        for(int i=1; i <= message.getBytes().length; i++) messageBytesEncoded[i] = message.getBytes()[i-1];

        Sha256Hash messageHash = Sha256Hash.of(messageBytesEncoded);
        Sha256Hash messageDoubleHash = Sha256Hash.of(messageHash.getBytes());
  • FYI you could use "\030Bitcoin Signed Message:\n\0".getBytes() – dave_thompson_085 Jul 12 '20 at 6:32

The preimage used when signing messages with Bitcoin Core is a combination of the "Magic" phrase: Bitcoin Signed Message:\n and the Message you are signing. However there is one caveat, being you must have a VarInt prepended to both the "Magic" and the Message. The VarInt encodes the byte-length of the message (encoded in UTF-8). Since the "Magic" value never changes, its VarInt value will always be 0x18.

For your example, the message is nothing, therefore your VarInt will be 0x00. Your preimage and sighash becomes:

Preimage: 0x18426974636f696e205369676e6564204d6573736167653a0a00
Sighash: SHA256(SHA256(preimage)) = 0x80e795d4a4caadd7047af389d9f7f220562feb6196032e2131e10563352c4bcc

    0x18       0x426974636f696e205369676e6564204d6573736167653a0a        0x00      (empty)
MAGIC_VARINT                       MAGIC                           MESSAGE_VARINT  MESSAGE

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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