¿Por qué mi transacción sin procesar no es canónica, obtiene el error -25 y se rechaza?

Estoy tratando de trasladar la lógica de generación de TX a un idioma diferente (e impopular). Me he estado golpeando la cabeza contra esto demasiado tiempo y debería haberme ido, pero bueno, ya sabes, el cierre y la terquedad.

Aquí está mi código hasta ahora.

Blockchain.info, Electrum y Bitcoin Core descodifican mi intento de TX en algo de aspecto razonable, como:

{
"txid" : "49c210ae472c5b5e39447e1f6d9bc020cd0f0075cc03d919afb0857a964e1f41",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid" : "b097384c42a3be2730db3e3720a1806c76172b6b62b2b5ee007c2c6fd295cadf",
"vout" : 1,
"scriptSig" : {
"asm" :     "30460221009f478737296e39bbcff2ef7c6f013acc25bea75941acd8573bcea83ca910018b022100e9bff518297fec344d6e7e42cabc5ec793c4be507af68210bdde803a8a2958ed0104d8f39341451e2e66a00ef010c815f1284bc5e3a187476aab319b2d127b7e219c9b9e68bff311c63474242a9baab34f7ddec05de2c45bd140a74a64621ccb42cb",
"hex" :   "4930460221009f478737296e39bbcff2ef7c6f013acc25bea75941acd8573bcea83ca910018b022100e9bff518297fec344d6e7e42cabc5ec793c4be507af68210bdde803a8a2958ed014104d8f39341451e2e66a00ef010c815f1284bc5e3a187476aab319b2d127b7e219c9b9e68bff311c63474242a9baab34f7ddec05de2c45bd140a74a64621ccb42cb"
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.00439999,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 7847eb9e366653aeb8857d541236fe4fd90c57e7 OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a9147847eb9e366653aeb8857d541236fe4fd90c57e788ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1BxzF8rgXtuiPuSN8azdMJgryzQSWt4Uoj"
]
}
}
]
}

Al intentar enviar una transacción en Bitcoin Core, aparece el "error -25". Al leer otras publicaciones de StackExchange y buscar en Google, las posibles causas se enumeran como:

"Al leer el código fuente, se devuelve este error cuando falla AcceptToMemoryPool, pero no cuando falla porque la transacción no es válida. ¿Debug.log emite algo cuando esto sucede?"

"Obtienes ese oscuro error de RPC cuando tu tx está usando salidas de las que Bitcoin nunca ha oído hablar". No aparece nada en ~/.bitcoin/debug.log.

Al intentar enviarlo con el servicio slash pushtx de blockchain.info (oh, vamos, ¿en serio no hay una lista blanca para los sitios a los que puedo vincularme?), obtengo "El script resultó en una pila no verdadera: []", que implica que cometí un error en mi lógica de firma en alguna parte.

He caminado a través de estos:

Y otros, incluidas varias implementaciones de Ruby, Python, Java y JavaScript, pero necesito más de 10 reputación para publicar más de 2 enlaces. No puedo por mi vida averiguar dónde me estoy desviando.

Sé que partes de esos están desactualizados. La clave privada con la que estoy comenzando (de Electrum) no está marcada como comprimida (33 bytes después de DecodeBase58Check y el último es 0x01), por lo que la clave pública en la transacción de la que estoy tratando de extraer tampoco debería estar comprimida.

El TX que estoy tratando de gastar ( b097384c42a3be2730db3e3720a1806c76172b6b62b2b5ee007c2c6fd295cadf , segunda salida, también conocido como 1) con este TX tiene un script de salida que es Pay to Public Key Hash (d9495c762aed3dba15eec648beb3b55a1a4) ​​según blockchain infobd. El scriptPubKey completo del prevout es:

OP_DUP OP_HASH160 d9495c762aed3dba15eec648beb55a8a43b8d1bd OP_EQUALVERIFY OP_CHECKSIG

Mi clave privada, decodificada de WIF y ejecutada a través de ecdsa::pub_from_priv(), ecdsa::pub_encode(), coincide con ese valor.

Parece que obtener el hash correcto antes de que el hash se firme en la firma sería la parte difícil. Estoy agregando 0x01 a la firma después de que sale de ecdsa::Sign(). Antes de firmar el TX, se le agrega 01000000. scriptSig (el script en la entrada para el TX que estoy construyendo) es "OP_DUP, OP_HASH160, PUSHDATA, dirección de Bitcoin (hash de clave pública), OP_EQUALVERIFY, OP_CHECKSIG" antes de que se firme, luego se convierte en un varstr del sig y el pubKey , y que se decodifica en un código de bytes de aspecto razonable:

guionSig:

0: OP_PUSHDATA 0x304602210094c538663c149f40929bb787d6174104a694181d063943a745e558b17d09e276022100b1812105ea6d7a8206c9019303a6459a9a2b2524b364debe69405b5d8b90c6c301
74: OP_PUSHDATA 0x04d8f39341451e2e66a00ef010c815f1284bc5e3a187476aab319b2d127b7e219c9b9e68bff311c63474242a9baab34f7ddec05de2c45bd140a74a64621ccb42cb

scriptPubKey:

0: OP_DUP
1: OP_HASH160
2: OP_PUSHDATA 0x7847eb9e366653aeb8857d541236fe4fd90c57e7
23: OP_EQUALVERIFY
24: OP_CHECKSIG

El uso de la antigua scriptPubKey como scriptSig se aproxima y supone un simple pago por firma en lugar de sacar la scriptPubKey exacta del TX que se está dibujando, pero en este caso simple, parecen coincidir.

Aquí está el TX sin procesar (¿incorrectamente?) firmado:

0100000001dfca95d26f2c7c00eeb5b2626b2b17766c80a120373edb3027bea3424c3897b0010000008b48304502202647239b48610693967a24c7c976f0df903891113c56f50b6e3368c3f73eefb0022100e372ab8bf76ab35cba1ce6c1890fb6b27bb4d6d54f080180b46a6ecc2ae09248014104d8f39341451e2e66a00ef010c815f1284bc5e3a187476aab319b2d127b7e219c9b9e68bff311c63474242a9baab34f7ddec05de2c45bd140a74a64621ccb42cbffffffff01c0b60600000000001976a9147847eb9e366653aeb8857d541236fe4fd90c57e788ac00000000

Aquí está el código:

https://gist.github.com/scrottie/15f2fca963d164306dcb

PrivateKey a pedido (hay $ 1 allí).

Si alguien puede volcar eso y descubrir dónde me equivoqué, estaría muy agradecido.

(1) La firma no es válida (2) La firma debe estar en "formato de valor S bajo" (3) ¡UTILICE TESTNET PARA TALES EXPERIMENTOS!
Lo siento por la falta de uso de testnet. Si puedo hacer que esto funcione, intentaré crear un buen tutorial para eso, pero los existentes son para la red principal. Conecté la clave privada/desde la dirección/hasta la dirección/cantidad de BTC/hash de TX anterior de bitcoin.stackexchange.com/questions/3374/… y obtuve exactamente el mismo hash de firma: 9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e Comparando byte por byte, es idéntico hasta allí . Luego, la K aleatoria tiene el signo cambiado. Coloréame molesto.
Realmente desearía saber qué era la K en ese ejemplo para poder reproducirlo completamente...
No puedo encontrar nada en Google en "Low-S-Value-Format". ¿Te refieres simplemente a endianness? Tienes una referencia para eso? Gracias por su respuesta.
Buscar "maleabilidad de la firma ECDSA"

Respuestas (2)

%&@!

Se solucionó con este cambio:

$privateKey = $privateKey->as_hex();  $privateKey =~ s{^0x}{} or die;
warn sprintf "doing: python sig.py  '%s' '%s'\n", $privateKey, to_hex($s256);
open my $python, '-|', 'python', 'sig.py', $privateKey, to_hex($s256) or die $!;
my $sig = readline $python;
warn "python says for sig: $sig\n";
$sig =~ s{[^0-9a-fA-F]}{}g;
$sig = from_hex($sig);

Y agregando este archivo como sig.py:

import ecdsa
import ecdsa.der
import ecdsa.util

import sys

privateKey = sys.argv[1]
s256 = sys.argv[2]

# print("privateKey len: ");  print(len(privateKey.decode('hex')))
# print(privateKey)

# print("s256 len: ");  print(len(s256.decode('hex')))
# print(s256)

sk = ecdsa.SigningKey.from_string(privateKey.decode('hex'), curve=ecdsa.SECP256k1)
sig = sk.sign_digest(s256.decode('hex'), sigencode=ecdsa.util.sigencode_der) + '\01' # 01 is hashtype
print sig.encode('hex')

Soy nuevo aquí y aparentemente me estoy trolleando. No pude hacer funcionar los módulos en CPAN que interactúan con openssl, así que tenía una opción para la biblioteca EC, algo escrito en Perl puro. Eso aparentemente tiene problemas, a pesar de que pasa las pruebas unitarias. O lo estaba usando mal.

Eso resultó en una salida que hizo que https://blockchain.info/pushtx dijera "Transacción enviada". Y luego las monedas se movieron.

Gracias a todos los que se detuvieron para echarle un ojo a esto. Te debo una cerveza. Intentaré seguir adelante con muy, muy buenos comentarios y documentos.

Lo siento, no uso lenguaje perl y no tengo instalado el entorno perl para depurar su código

Echa un vistazo a ¿Cómo canjear un Tx básico?

No veo el paso n.º 13 en el que debe agregar 01000000 = SIGHASH_ALL a los datos que inicia en su método makeRawTransaction

Revisé Cómo canjear un Tx básico, la especificación del protocolo, la descripción de OP_CHECKSIG en bitcoin.it (URL limitadas para mí hasta que obtenga más puntos de StackExchange) y en.bitcoin.it/w/images/en/7/ 70/Bitcoin_OpCheckSig_InDetail.png . pack("V") o pack("L<") en Perl es como pack("<L") en Python. De lo contrario, sigue bastante de cerca. SIGHASH_ALL está en esta línea: my $myTxn_forSig = makeRawTransaction ($outputTransactionHash, $sourceIndex, $scriptPubKey, $outputs). "01000000"; Gracias por echarle un vistazo.