The bitcoinj
library's API offers a signMessage
method of the ECKey
class which returns a signature (r,s)
as 65 bytes encoded as a Base64 string. The crunch of the encoding lies in the additional leading byte which allows key recovery (the 64 bytes of (r,s)
alone are not enough for that purpose). In particular, the leading byte of the encoding includes information regarding the compression status of the signing key.
Now I assume this specific signature encoding was implemented for a very good reason, which I suspect is the fact that it is a standard encoding, present in many other bitcoin libraries and in core.
Now when verifying a signature through key recovery, the verifyMessage
method ignores the compression status of the recovered key and simply ensures that the corresponding elliptic curve points match. Sure, this is a pretty good check, but why do we bother encoding the compression status of a signing key, if we are going to ignore it later? Is this the semantics other known libraries have adopted, or indeed core? I attach a small snippet for illustration:
import org.bitcoinj.core.ECKey;
import java.security.SignatureException;
public class Test
{
public static void main(String[] args)
{
String message = "some arbitrary message";
ECKey k1 = new ECKey(); // random , compressed
ECKey k2 = k1.decompress();
// Signature (r,s) encoded as 65 bytes, with leading byte
// allowing key recovery (including compression status)
String sig1 = k1.signMessage(message);
String sig2 = k2.signMessage(message);
// compression status is encoded in signature => differing leading byte
System.out.println(sig1); // INgDhkt98Mme9m9AQ+nqtjyvjj ...
System.out.println(sig2); // HNgDhkt98Mme9m9AQ+nqtjyvjj ...
// signatures are verified succesfully
try
{
k1.verifyMessage(message, sig1); // compressed case
}
catch(SignatureException e)
{
System.out.println("it should not happen");
}
try
{
k2.verifyMessage(message, sig2); // uncompressed case
}
catch(SignatureException e)
{
System.out.println("it should not happen");
}
// in fact, compression status is ignored ...
try
{
k1.verifyMessage(message, sig2); // should it throw ?
}
catch(SignatureException e)
{
System.out.println("it does not happen");
}
}
}