Firme el legado de transacciones con Openssl

Quiero entender cómo Bitcoin firma transacciones con Openssl. Vi esta publicación pero tengo algunos problemas.

Por encima de mis claves privadas y públicas.

Claves privadas:

$ cat chiave_priv_3.pem 

    -----BEGIN EC PARAMETERS-----
    BgUrgQQACg==
    -----END EC PARAMETERS-----
    -----BEGIN EC PRIVATE KEY-----
    MHQCAQEEIOLcjvH4RhZFl1hthXl7DD3MHXUCWKiI2b/zoYlvBmKboAcGBSuBBAAK
    oUQDQgAE3Qc0PacS/HmhnZjIot48dZ++rvh121Rq+xjreFKrf/QCl2sDXTncDbe0
    wcCtq4yaUpdbmzV9OgrP94EFsEC/1w==
    -----END EC PRIVATE KEY-----

clave pública comprimida

$ cat chiave_pubblica_compressa_3.txt 
03dd07343da712fc79a19d98c8a2de3c759fbeaef875db546afb18eb7852ab7ff4

Estoy trabajando en el registro y mi UTXO es:

 {
    "txid": "0c647aadcb4028260ecb753c727e0237658873a0cbdbeb5695b8cb85ea87f98d",
    "vout": 0,
    "address": "myiGTzGG8rJikr2HcVyPYqprh6Ds1kdY7v",
    "label": "",
    "scriptPubKey": "76a914c79602205abbe1d35ee6dcb4a19791cbd5a26e1588ac",
    "amount": 49.99900000,
    "confirmations": 6,
    "spendable": true,
    "solvable": true,
    "desc": "pkh([c7960220]03dd07343da712fc79a19d98c8a2de3c759fbeaef875db546afb18eb7852ab7ff4)#5c03vyak",
    "safe": true
  },

Puedo crear datos de transacciones con estos parámetros:

TXID=0c647aadcb4028260ecb753c727e0237658873a0cbdbeb5695b8cb85ea87f98d
VOUT=0
AMOUNT=49.998
ADDR_MITT=msPjuNgbmbRSkNGJPvquKJRRmrbzS96s62

bitcoin-cli createrawtransaction '[{"txid":"'$TXID'","vout":'$VOUT'}]' '[{"'$ADDR_MITT'":'$AMOUNT'}]'

02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c0000000000ffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac00000000

Ahora obtengo el scriptPubKey y coloco ScriptSig y agrego SIGHASH en little endian.

02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c000000001976a914c79602205abbe1d35ee6dcb4a19791cbd5a26e1588acffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac0000000001000000

Dónde

02000000 
01
8df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c
00000000
19 76a914c79602205abbe1d35ee6dcb4a19791cbd5a26e1588ac
ffffffff
01
c0e4022a01000000
19 76a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac
00000000
01000000

Sha256 dos veces

$ printf 02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c000000001976a914c79602205abbe1d35ee6dcb4a19791cbd5a26e1588acffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac0000000001000000 | xxd -r -p | sha256sum -b | xxd -r -p | sha256sum -b | awk '{ print $1 }' > a.txt
daac47088b7eba5593282013442dbcba59556d095982d470f0a02972269bc0e1 

Regístrate con chiave_priv_3.pem

$ openssl dgst -sha256 -hex -sign chiave_priv_3.pem a.txt | sed 's/^.* //'                                                           
304402207071c9fd7341a15cacfea72e215b44bed42d1a00c7fc5bfed5e01d36acd953c20220308e45aedd080ba65993b69b4c82cc928db8c3e70a6f402fe0258157a4690c6e

Agregue 01 al final de la firma y verifique la longitud

printf 304402207071c9fd7341a15cacfea72e215b44bed42d1a00c7fc5bfed5e01d36acd953c20220308e45aedd080ba65993b69b4c82cc928db8c3e70a6f402fe0258157a4690c6e01 | wc -c
 142

142 char is 47

Firma DER de depuración

30 DER prefix
44 Length of rest of Signature
02 Marker for r value
20 Length of r value
r = 7071c9fd7341a15cacfea72e215b44bed42d1a00c7fc5bfed5e01d36acd953c2
02 Marker for s value
20 Length of s value
s = 308e45aedd080ba65993b69b4c82cc928db8c3e70a6f402fe0258157a4690c6e
01 SIGHASH_ALL

concatenar con clave pública comprimida

47304402207071c9fd7341a15cacfea72e215b44bed42d1a00c7fc5bfed5e01d36acd953c20220308e45aedd080ba65993b69b4c82cc928db8c3e70a6f402fe0258157a4690c6e012103dd07343da712fc79a19d98c8a2de3c759fbeaef875db546afb18eb7852ab7ff4

la longitud completa de scriptSig es: 6A (hexadecimal de 212 caracteres) Ahora puedo construir mi transacción:

02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c000000006A47304402207071c9fd7341a15cacfea72e215b44bed42d1a00c7fc5bfed5e01d36acd953c20220308e45aedd080ba65993b69b4c82cc928db8c3e70a6f402fe0258157a4690c6e012103dd07343da712fc79a19d98c8a2de3c759fbeaef875db546afb18eb7852ab7ff4ffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac00000000

enviartransacción

bitcoin-cli sendrawtransaction 02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c000000006A47304402207071c9fd7341a15cacfea72e215b44bed42d1a00c7fc5bfed5e01d36acd953c20220308e45aedd080ba65993b69b4c82cc928db8c3e70a6f402fe0258157a4690c6e012103dd07343da712fc79a19d98c8a2de3c759fbeaef875db546afb18eb7852ab7ff4ffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac00000000
error code: -26
error message:
mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation) (code 16)`

Respuestas (1)

El problema es que está haciendo todo el hashing (y poniéndolo en hexadecimal) y luego haciendo que OpenSSL lo vuelva a hacer. openssl dgstcodificará el mensaje antes de firmar, pero esto es incorrecto para Bitcoin. Tradicionalmente, en ECDSA, el mensaje se codifica una vez y luego se firma. Pero con Bitcoin, en realidad se codifica dos veces. Otra forma de pensar en esto es que el mensaje es un hash.

Así que ya ha duplicado la transacción. Pero OpenSSL lo codificará nuevamente, haciéndolo con triple cifrado, lo que significa que usted firma un mensaje diferente. Además, OpenSSL leerá su mensaje como datos binarios, no interpretará el hexadecimal que le dio. Así que realmente tu comando hash debería ser

printf 02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c000000001976a914c79602205abbe1d35ee6dcb4a19791cbd5a26e1588acffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac0000000001000000 | xxd -r -p | sha256sum -b | xxd -r -p > a.txt

Esto codificará el mensaje sighash una vez y colocará los datos binarios en a.txt.

Entonces puedes firmarlo como lo hiciste.


Alternativamente, OpenSSL tiene una pkeyutlherramienta que no hace ningún hash adicional en los datos que se firmarán y, en cambio, espera que ya los hayas hash. Podría usar esto en su lugar, pero aún necesitará que su hash sea binario en lugar de hexadecimal. Entonces puedes hacer

printf 02000000018df987ea85cbb89556ebdbcba073886537027e723c75cb0e262840cbad7a640c000000001976a914c79602205abbe1d35ee6dcb4a19791cbd5a26e1588acffffffff01c0e4022a010000001976a914824441111b374bec1952a5b3fa9dd4e3ed679b3888ac0000000001000000 | xxd -r -p | sha256sum -b | xxd -r -p | sha256sum -b | xxd -r -p > a.txt

para obtener el doble hash. Entonces hazlo

openssl pkeyutl -inkey chiave_priv_3.pem a.txt -sign -in a.txt -pkeyopt digest:sha256 | xxd -p -c 256

para firmar.

Gracias por responder, probé ambas soluciones pero me sale el mismo error. la segunda solución tiene un pequeño erroropenssl pkeyutl -inkey chiave_priv_3.pem -sign -in a.txt -pkeyopt digest:sha256 | xxd -p -c 256
Funciona pero a veces me sale este errormandatory-script-verify-flag-failed (Non-canonical signature: S value is unnecessarily high) (code 16)
Eso es de esperar. Hay una regla de estandarización que requiere que el valor de S sea bajo. Puede saber si el valor de S es bajo comprobando su longitud; debe ser de 32 bytes o menos. Una S alta se puede convertir en una S baja al negarla, pero si no sabe cómo hacerlo manualmente, simplemente haga firmas hasta que una tenga una S baja. Hay un 50 % de probabilidad de que cualquier firma tenga una S baja. .
gracias por su tiempo, voy a repetir la firma por ahora. El siguiente paso es firmar P2SH personalizado.