Creación de firma de testigo: bandera de verificación de secuencia de comandos no obligatoria (la firma debe ser cero para la operación CHECK(MULTI)SIG fallida)

Así que estoy tratando de hacer mis propias firmas de testigos como se describe en bip143. Tengo un código que verifica correctamente la firma de ejemplo, así que pensé que tenía la firma y la verificación básicas. Tengo un código que puede verificar las firmas en bip143 correctamente y, al usar el mismo código, verifico este resultado correctamente, por lo que no estoy seguro de cómo determinar qué está fallando. el error que me sale es

error code: -26
error message:
non-mandatory-script-verify-flag (Non-canonical DER signature)

Sospecho que no estoy invirtiendo el orden de bytes en algo, pero realmente no estoy seguro de cómo diagnosticar esto.

----transacción sin firmar-------
020000000102303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda400000000000ffffffff02400d03000000000016001453f0b90d7be69d4f5f1356908a9e12fef93561df74e0022a010000001600140f54e16bd59da94840741b69ab9b9db99467a85800000000
-------finalizar transacción sin firmar------
dhash(prevouts)=02303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda4000000000
dhash(secuencias)=ffffffff
dhash(salidas)=400d0300000000000001453f0b90d7be69d4f5f1356908a9e12fef93561df74e0022a0100000000140f54e16bd59da94840741b69ab9b9db99467a858
inversión: 02000000
hashPrevouts: 9ebc8589966e2dd13cd64af1835262c2d8e931b4388a8ebe76620814b4f407bd
secuencia hash: 3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044
punto de salida: 02303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda4000000000
código de secuencia de comandos: 1976a914992fda25fdf8447092aa223a34bedd3572173d8688ac
cantidad: 00f2052a01000000
nSecuencia: ffffffff
hashSalidas a54e6a8744fa773a5f1c64d7f60d53efd69f8d76e85e7817970a87990874e4c1
nLockTime 00000000
nHashType 01000000
pre hash image: 020000009ebc8589966e2dd13cd64af1835262c2d8e931b4388a8ebe76620814b4f407bd3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e7066504402303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda40000000001976a914992fda25fdf8447092aa223a34bedd3572173d8688ac00f2052a01000000ffffffffa54e6a8744fa773a5f1c64d7f60d53efd69f8d76e85e7817970a87990874e4c10000000001000000
imagen punteada: 4f8fce636bb4a44aa6a241e9597bbe54fce3bfe3426a49a94c5b006d42eac007
rawsig: 2cbb748d06a1b87c933a3118d8c2a5a0ab02111bae777a9e52ba8571eee2d549e80a6e4cef24a172e64d11aa7b83c8005fe6fc078307b9e663ef9eb14fce05a3
der sig: 304402202cbb748d06a1b87c933a3118d8c2a5a0ab02111bae777a9e52ba8571eee2d5490220e80a6e4cef24a172e64d11aa7b83c8005fe6fc078307b9e463fce05e6a3
clave de publicación de script: 992fda25fdf8447092aa223a34bedd3572173d86
clave de pub: 0392ff36d0ae9f3a74c0483fd309ff9144972b1dce6d6dfe4d9de474c721b36521
----transacción firmada-------
0200000000010102303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda400000000000ffffffff02400d03000000000016001453f0b90d7be69d4f5f1356908a9e12fef93561df74e0022a010000001600140f54e16bd59da94840741b69ab9b9db99467a8580247304402202cbb748d06a1b87c933a3118d8c2a5a0ab02111bae777a9e52ba8571eee2d5490220e80a6e4cef24a172e64d11aa7b83c8005fe6fc078307b9e663ef9eb14fce05a301210392ff36d0ae9f3a74c0483fd309ff9144972b1dce6d6dfe4d9de474c721b3652100000000
-------finalizar transacción firmada------

Esto se está ejecutando en mi registro. Puedo proporcionar cualquier información adicional que la gente pueda pensar que sería útil. He pasado muchas horas comparando todo lo posible con todos los ejemplos antes de publicar aquí. Sé que dice non connical der sig, pero en mi investigación, otras personas dicen que el error ocurre cuando muchas cosas diferentes salen mal. Además, cuando comparo la firma con las firmadas del núcleo, coinciden exactamente en formato con "30440220", luego 32 bytes, luego 0220 y luego los otros 32 bytes. Lo único que es diferente son los datos de firma reales. Además, recibo el mismo error cuando uso una salida DER cónica nativa. Cualquier ayuda es muy apreciada como siempre
-----ACTUALIZACIÓN-----
entonces, el problema con el script anterior es que principalmente no está codificado en DER correctamente, lo que significa que si s o r most MSB> 0x80 lo trata como negativo porque es un entero con signo. En este ejemplo, el MSB de S es 0xE8, que es mayor que 0x80. bitcoin tiene una política que en realidad debe ser menor que el valor "n" de la curva ecdsa. Primero debajo está el código que corrige el problema, y ​​puedo verificar la firma (pero el error aún existe en el núcleo de bitcoin). Código para invertir S en python:

n=115792089237316195423570985008687907852837564279074904382605163141518161494337
s = bytearray(s_bytes)
sint = 0
cnt = 31
for bb in s:
    sint += bb << cnt*8
    cnt -= 1
if sint >= n/2:
    print("Negated")
    sint = n-sint
    s = bytearray(sint.to_bytes(32, byteorder='big'))

no está mal. Y en cuanto a r, el núcleo de bitcoin simplemente vuelve a firmar hasta que es inferior a 0x80. es legal que sea mayor si el codificador der rellena correctamente el MSB). Así lo hizo de la misma manera. a continuación hay una nueva transacción que tiene el problema corregido, aunque sigo teniendo el mismo error :-/.

----transacción sin firmar-------
020000000102303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda400000000000ffffffff02400d03000000000016001453f0b90d7be69d4f5f1356908a9e12fef93561df74e0022a010000001600140f54e16bd59da94840741b69ab9b9db99467a85800000000
-------finalizar transacción sin firmar------
dhash(prevouts)=02303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda4000000000
dhash(secuencias)=ffffffff
dhash(salidas)=400d0300000000000001453f0b90d7be69d4f5f1356908a9e12fef93561df74e0022a0100000000140f54e16bd59da94840741b69ab9b9db99467a858
inversión: 02000000
hashPrevouts: 9ebc8589966e2dd13cd64af1835262c2d8e931b4388a8ebe76620814b4f407bd
secuencia hash: 3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044
punto de salida: 02303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda4000000000
código de secuencia de comandos: 1976a914992fda25fdf8447092aa223a34bedd3572173d8688ac
cantidad: 00f2052a01000000
nSecuencia: ffffffff
hashSalidas a54e6a8744fa773a5f1c64d7f60d53efd69f8d76e85e7817970a87990874e4c1
nLockTime 00000000
nHashType 01000000
pre hash image: 020000009ebc8589966e2dd13cd64af1835262c2d8e931b4388a8ebe76620814b4f407bd3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e7066504402303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda40000000001976a914992fda25fdf8447092aa223a34bedd3572173d8688ac00f2052a01000000ffffffffa54e6a8744fa773a5f1c64d7f60d53efd69f8d76e85e7817970a87990874e4c10000000001000000
imagen hash: 4f8fce636bb4a44aa6a241e9597bbe54fce3bfe3426a49a94c5b006d42eac007
rawsig: 38d610fa975440d09b501864185e656546015ee98dd8b5b58eb41be547b6a686be784798307a123bdfefce7463b6e7203b69d18950ea95ae684dddb5a9bcaeaf
s antes: be784798307a123bdfefce7463b6e7203b69d18950ea95ae684dddb5a9bcaeaf
negado
s después de: 4187b867cf85edc42010318b9c4918de7f450b5d5e5e0a8d578480d726799292
der sig: 3044022038d610fa975440d09b501864185e656546015ee98dd8b5b58eb41be547b6a68602204187b867cf85edc42010318b9c4918de7f450b5d5e5e0a789d29284
clave de publicación de script: 992fda25fdf8447092aa223a34bedd3572173d86
clave de pub: 0392ff36d0ae9f3a74c0483fd309ff9144972b1dce6d6dfe4d9de474c721b36521
----transacción firmada-------
0200000000010102303e285bb228344346acb5292397a761103efcd0182c4cf95b4d01b2bdda400000000000ffffffff02400d03000000000016001453f0b90d7be69d4f5f1356908a9e12fef93561df74e0022a010000001600140f54e16bd59da94840741b69ab9b9db99467a85802473044022038d610fa975440d09b501864185e656546015ee98dd8b5b58eb41be547b6a68602204187b867cf85edc42010318b9c4918de7f450b5d5e5e0a8d578480d72679929201210392ff36d0ae9f3a74c0483fd309ff9144972b1dce6d6dfe4d9de474c721b3652100000000
-------finalizar transacción firmada------

Nuevamente se agradece la ayuda

Esa no es una codificación DER válida. Cuando el primer byte superior es >= 0x80, el número representado se interpreta como negativo. Cuando el valor codificado es mayor que 2^255, eso significa que se debe agregar un 0 byte adicional al frente. La red Bitcoin en realidad tiene una restricción adicional, que requiere que el valor S en la firma sea inferior a n/2. Esto es muy fácil: cuando s >= n/2, negarlo (reemplazar con ns).
Lo siento, tenía que probarlo. ok, cuando estaba usando el formato DER canónico nativo, a veces tenía el 0 inicial y era uno más largo. Entonces, para asegurarme de que entiendo, básicamente, cuando es más grande que 0x80 en lugar de solo agregar el 00, ¿negamos la S completa? entonces si s[0] > 0x80, s = 0xFFFFFFFF...-s?
@PieterWuille Probé lo que pensé que era correcto. sin éxito todavía. negar S cuando MSB es 1 y no agregar el cero inicial no validará en general ahora. ¿Cómo sabe bitcoin core si lo has invertido o no?
La razón para hacer esto es la maleabilidad: no queremos que terceros transmitan una transacción para cambiarla de manera que txid cambie pero sin invalidarla. Resulta que para ECDSA, si (r,s) es una firma válida, también lo es (r,ns). Para evitar esa manipulación, Bitcoin Core tiene una regla de política que solo transmite transacciones con firmas cuyo valor s es < n/2. No necesita saber si fue negado o no, ya que ambas formas son ECDSA igualmente válidas.
Tenga en cuenta que n es un número bastante no trivial, es alrededor de 2 ^ 256 - 1.5 * 2 ^ 128 (busque el orden de la curva secp256k1), y R y S y codificado en notación big endian.
ok, obtuve sn y mi programa de validación de prueba aún puede validar la firma. Para el núcleo de bitcoin, ¿qué pasa con r? si es demasiado grande para el valor máximo, ¿estamos bien, solo agregamos el 0 inicial? y siguiendo un esquema der "predeterminado"?
Sí, R solo tiene que estar en el rango [0,n). Sin embargo, dado que existe un 50% de posibilidades de que el valor sea inferior a 2^255, puede guardar un byte a menudo simplemente intentando iniciar sesión nuevamente. Eso es lo que hace Bitcoin Core, pero solo por razones financieras (las tarifas varían según el tamaño de la firma); es perfectamente legal tener valores R de 33 bytes.
@PieterWuille ok Así que actualicé el ejemplo con las correcciones. todavía se decodifica bien, y todavía puedo validar la firma yo mismo. sendrawtransaction todavía tiene el mismo error :-/
¿Cuál es su firma codificada DER ahora?
3044022038d610fa975440d09b501864185e656546015ee98dd8b5b58eb41be547b6a68602204187b867cf85edc42010318b9c4918de7f450b5d5e5e0a8d578489d72 porque es nuevo porque es una firma aleatoria. Creo que todo lo demás debería ser igual. En la actualización publiqué el original y la S negada solo para completar
Eso se ve correctamente codificado en DER. Si sigues teniendo el mismo problema, no tengo ni idea.
@PieterWuille está bien, agradezco la ayuda/verificación. gracias

Respuestas (1)

ok lo descubrí! después de una semana :-D. Quiero publicar esto porque pasé la última semana aprendiendo el núcleo de bitcoin para generar los valores intermitentes durante el sighash para comparar. Cualquier otra persona que necesite entender realmente cómo funciona el núcleo, espero que esto sea realmente útil. Primero, mi problema específico: mi secuencia de comandos de salida era completamente incorrecta, creo que confundí la secuencia de comandos de bloqueo y de desbloqueo, lo que aún no debería invalidar la firma, pero también notará que en el ejemplo la salida es como:

202cb20600000000 1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac

bueno, el primer 0x19 no es parte de la clave pública del script (la forma en que lo muestra la transacción sin procesar). Así que en la descripción donde dice

Si el tipo de sighash no es SINGLE ni NONE, hashOutputs es el doble SHA256 de la serialización de toda la cantidad de salida (8 bytes little endian) con scriptPubKey (serializado como scripts dentro de CTxOuts);

hash con el prefijo de longitud 0x19. Incluí la longitud en la transacción general, por lo que todavía se decodificó bien, pero no en la forma en que lo hice. Sencillo malentendido.

A continuación se muestra cómo construí y modifiqué el núcleo (0.21.1) para generar los valores para resolver esto

  1. Principalmente seguí este ejemplo https://jonatack.github.io/articles/how-to-compile-bitcoin-core-and-run-the-tests , básicamente instalé la mayoría de las dependencias de esa lista
  2. exportar BDB_PREFIX='/home/ubuntu/btc/bitcoinsource/bitcoin-0.21.1/db4'
  3. ./configure BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" BDB_CFLAGS="-I${BDB_PREFIX}/include" --enable-debug
  4. hacer
  5. Observe el uso de --enable-debug. Creo que esto era necesario para permitirte usar gdb. Ocasionalmente obtuve errores de bloqueo de subprocesos en el archivo de registro, lo que provocó que se bloqueara en el modo de depuración al usar la API de la billetera.
  6. Creé una carpeta bin separada con algunos enlaces simbólicos a los 4 ejecutables binarios principales. luego editó /etc/environment para agregar la ruta a esa carpeta. luego ejecutó "source /etc/environment". Esto le permite ejecutar los binarios desde cualquier lugar.
  7. ahora puede ejecutar bitcoind -regtest -daemon. Desde aquí, puede acceder al código fuente donde se crearon los archivos binarios y ejecutar gdb, y adjuntarlos al proceso.
  8. Modificar <bitcoin_dir>/src/script.interpreter.cpp En la parte superior, escribí una función básica para tomar algunos tipos de variables diferentes e imprimir en hexadecimal. Disculpas por el código incorrecto, soy una persona C de oficio, no C++. Creé un archivo de registro separado para no meterme con el registro principal de bitcoind
std::string hexhalftostr (valor de carácter sin signo)
{
    si (valor <= 9)
        devuelve std::to_string(val);
    si no (val == 10)
        devolver "A";
    si no (val == 11)
        devolver "B";
    si no (val == 12)
        devuelve "C";
    si no (val == 13)
        devuelve "D";
    si no (val == 14)
        devuelve "E";
    si no (val == 15)
        devuelve "F";
    si no regresa "";
}
anular logbytes (caracter sin firmar *datos, int len)
{
    std::ofstream miarchivo;
    miarchivo.open ("/home/ubuntu/btc/bitcoinsource/bitcoin-0.21.1/special.log", std::ios_base::app);
    estándar::cadena ss = "";
    para (int i = 0;i < len;i++)
    {
        char lsb, msb;
        lsb = 0x0f & datos[i];
        msb = 0x0f & (datos[i] << 4);
        ss += hexhalftostr(msb) + hexhalftostr(lsb);
    }
    miarchivo << ss;
    miarchivo.close();
}
anular logbigint (datos uint256)
{
    logbytes(datos.datos(), 32);
}
inicio de sesión nulo (datos int sin firmar)
{
    arrayOfByte de caracteres sin firmar [4];
    para (int i = 0; i < 4; i++)
        arrayOfByte[i] = (datos << (i * 8));
    logbytes(matrizDeBytes, 4);
}
void loglong (datos largos sin firmar)
{
    char sin firmar arrayOfByte[8];
    para (int i = 0; i < 8; i++)
        arrayOfByte[i] = (datos << (i * 8));
    logbytes(matrizDeBytes, 8);
}
texto de registro vacío (std::string ss)
{
    std::ofstream miarchivo;
    miarchivo.open ("/home/ubuntu/btc/bitcoinsource/bitcoin-0.21.1/special.log", std::ios_base::app);
    miarchivo << ss;
    miarchivo.close();
}

  1. SignatureHash es la función en la que podemos generarlos a medida que se calculan
logtext("\nnVersión: ");
        logint(txTo.nVersion);
        logtext("\nhashPrevouts: ");
        logbigint(hashPrevouts);
        logtext("\nhashSquence: ");
        logbigint(hashSequence);
        logtext("\npunto de salida: ");
        logbigint(txTo.vin[nIn].prevout.hash);
        logint(txTo.vin[nIn].prevout.n);
        // no se como tomar los bytes
        ////logtext("\nscriptCode: ");
        ////logbigint(códigoscript);
        logtext("\ncantidad: ");
        loglong(cantidad);
        logtext("\nnSecuencia: ");
        logint(txTo.vin[nIn].nSequence);
        logtext("\nhashOutputs: ");
        logbigint(salidas hash);
        logtext("\nnTiempo de bloqueo: ");
        logint(txTo.nLockTime);
        logtext("\ntipohash: ");
        inicio de sesión (nHashType);

Y el retorno de la función tuvo que ser reemplazado porque el hash sha256 consume los datos, por lo que

 return ss.GetHash();

se convierte

 uint256 sighash = ss.GetHash();
 logtext("\nhashed image: ");
 logbigint(sighash);
 logtext("\n");
 //return ss.GetHash();
 return sighash;
  1. Recomendaría establecer un punto de interrupción y pasar por la función "EvalScript". Aquí es donde ocurre toda la magia, y es muy bueno para informarse sobre cómo se ejecuta el script de bitcoin real, porque cada código de error abarca errores prácticamente ilimitados, no estoy seguro de que haya otra forma de depurar su código sin hacer esto.