La bitcoinj
API de la biblioteca ofrece un signMessage
método de la ECKey
clase que devuelve una firma (r,s)
de 65 bytes codificada como una cadena Base64. El problema de la codificación radica en el byte inicial adicional que permite la recuperación de la clave (los 64 bytes por (r,s)
sí solos no son suficientes para ese propósito). En particular, el byte inicial de la codificación incluye información sobre el estado de compresión de la clave de firma.
Ahora asumo que esta codificación de firma específica se implementó por una muy buena razón, que sospecho es el hecho de que es una codificación estándar, presente en muchas otras bibliotecas de bitcoin y en el núcleo.
Ahora, al verificar una firma a través de la recuperación de clave, el verifyMessage
método ignora el estado de compresión de la clave recuperada y simplemente se asegura de que coincidan los puntos de la curva elíptica correspondiente. Claro, esta es una verificación bastante buena, pero ¿por qué nos molestamos en codificar el estado de compresión de una clave de firma, si vamos a ignorarlo más tarde? ¿Es esta la semántica que han adoptado otras bibliotecas conocidas, o de hecho la principal? Adjunto un pequeño fragmento para ilustración:
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");
}
}
}
El kilometraje puede variar con esta respuesta. La respuesta corta es probablemente no cuando se usa libbitcoin.
Justificación: Hace más de un año, cuando experimenté con el envío de fondos a una dirección sin comprimir usando una transacción firmada, las claves comprimidas correspondientes no se podían usar para gastar los mismos fondos, también ocurría lo contrario. El equilibrio dependía de la compresión de una clave. Cuando reciba fondos con una dirección sin comprimir, solo use la clave privada sin comprimir asociada para gastar. Del mismo modo, cuando reciba fondos con una dirección comprimida, solo use la clave privada comprimida asociada para gastar.
sven williamson
pieter wuille