A Bitcoind no le gusta el par ECDSA (r, s) producido por OpenSSL

Estoy escribiendo transacciones manualmente y me he topado con una situación bastante extraña.

Solo se acepta una de algunas de las transacciones que transmito a bitcoind; de lo contrario, obtengo una REJECT_NONSTANDARD(firma DER no canónica).

Así que me ensucie las manos y rastreé el rechazo que se originaba en esta línea: https://github.com/bitcoin/bitcoin/blob/9c5f0d542d1db507b3b9c87bd9de6d0d758d51c1/src/script/interpreter.cpp#L163

Leí sobre la codificación DER y verifiqué cómo IsValidSignatureEncodingse aplica, pero no sé por qué OpenSSL genera valores no compatibles con DER (r, s).

¿Cómo debo superar esto? Estoy pensando en algo similar a (pseudocódigo):

Pair (r, s);
do
{
   (r, s) = sign(hash, pvtkey);
} while (r[0] >= 128 || s[0] >= 128); // where r[0], s[0] should be the very first byte of each value

¿Pero no es eso un poco redundante? ¿Puedo darle a OpenSSL cualquier indicador para producir un par DER (R, S) válido en primer lugar?

¿Está utilizando OpenSSL para codificar el DER o escribió su propia función de codificación DER? Es muy fácil terminar con una función que solo es correcta para algunas entradas.

Respuestas (3)

Una posible razón es que Bitcoin Core espera un valor S bajo. Intente cambiar s con Ns si s > N/2 (N es el orden de la curva).

Fuente aquí

En realidad, ese no fue mi problema inicial, es un código de error completamente diferente que recibo ahora ( SCRIPT_ERR_SIG_HIGH_S) después de anteponer mi valor S defectuoso con un byte 0x00 (leí en otro hilo que debería hacerse si el primer byte de la S inicial es > = 128). Así que sí, lo más probable es que lo que tengo que hacer ahora sea lo que sugeriste.

El siguiente código de Python puede crear una firma codificada DER válida dada ry scomo objetos de byte:

def ser_sig_der(r, s):
    sig = b"\x30"

    # Make r and s as short as possible
    ri = 0
    for b in r:
        if b == "\x00":
            ri += 1
        else:
            break
    r = r[ri:]
    si = 0
    for b in s:
        if b == "\x00":
            si += 1
        else:
            break;
    s = s[si:]

    # Make positive of neg
    first = r[0]
    if first & (1 << 7) != 0:
        r = b"\x00" + r
    first = s[0]
    if first & (1 << 7) != 0:
        s = b"\x00" + s

    # Write total length
    total_len = len(r) + len(s) + 4
    sig += struct.pack("B", total_len)

    # write r
    sig += b"\x02"
    sig += struct.pack("B", len(r))
    sig += r

    # write s
    sig += b"\x02"
    sig += struct.pack("B", len(s))
    sig += s
    return sig

Es importante tener en cuenta que las firmas en Bitcoin también contienen un byte adicional adjunto a la firma codificada DER que representa el tipo sighash. Deberá agregar ese byte usted mismo.

El tercer byte codifica información sobre la longitud del contenido. Si la longitud del contenido es inferior a 127 bytes, el tercer byte es igual a la longitud del contenido. Si es mayor que 127 y menor que 255, el tercer byte es 0x81. Si es mayor que 255, el tercer byte es 0x82.

Los siguientes bytes indican la longitud del contenido.

Referencia: https://github.com/openssl/openssl/blob/master/crypto/asn1_dsa.c encode_der_length https://www.itu.int/rec/T-REC-X.690-201508-I/en

Aunque las firmas de bitcoin usan secp256k1 y low-S como señaló MikeD, todas las partes de valor nunca superan los 69 bytes y el segundo y tercer caso de codificación de longitud nunca ocurren. (El tercer caso en realidad es >=256 y <65536; por más tiempo, hay codificaciones de mayor longitud que no menciona, pero el código que vincula no las admite ni las necesita).