Skip to content

Message signing

A Bitcoin Signed Message proves that the holder of a key wrote a piece of text, without making a Transaction. The signed messages appendix explains the format. In Bitcoin4J the methods live on ECKey, which we get from PrivateKey.getKey() or PublicKey.getKey().

PrivateKey privateKey = PrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");
// The result is Base64, in the "Bitcoin Signed Message" format that wallets use.
String signature = privateKey.getKey().signMessage("hello");

The result is the 65-byte compact Signature in Base64, the same format that other wallets and libraries produce and accept. It is deterministic, i.e. signing the same message with the same key gives the same string.

// With the signer's Public Key in hand, verifyMessage() throws if the Signature does not match.
PublicKey signer = PublicKey.fromHex("029ddb9308c0dab96d047bbc99191ef60d3882e03dd8f49b8811abe1c9f0b3389d");
signer.getKey().verifyMessage("hello", signature);

verifyMessage() returns nothing on success. It throws java.security.SignatureException if the Signature is malformed, or was made by a different key or for a different message.

Quite often we only know the signer’s Address. It turns out that the compact Signature lets us recover the Public Key that made it, so we compare that key’s Address with the one we expected.

// With only an Address, we recover the Public Key from the Signature and compare Addresses.
ECKey recovered = ECKey.signedMessageToKey("hello", signature);
Address signedBy = Address.fromKey(NetworkAddressType.TEST_PKH, PublicKey.fromBytes(recovered.getPubKey()));
boolean valid = signedBy.toString().equals("mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L");

Recovery also tells us whether the signer used a compressed key, so the recovered key gives the same Address that the signer would show.