C# BC, creando y firmando transacciones sin procesar con éxito

Primera vez en stackexchange para pedir ayuda, lo siento si cometo errores (el inglés no es mi idioma nativo).

Intento crear una transacción sin formato válida en C# con bouncycastle y parece que me he quedado atascado, no puedo encontrar dónde está mi error. Pruebo en la red testnet y he usado muchas fuentes para ayudarme, principalmente esta . Para empezar, tengo la siguiente dirección y clave privada.

Address: mjhcWg5SvS96kk85R8G1wp7mru55UCNGY5
Public Key Hex: 0482052EF9560585ED62F046EE45C1B5F85448BCF1BD5CE36A7D35EB00C8A146C14BF99223907F9A8688E6F84B54FD747A637BB82F02E296203E735E7A6B40059F
Wif: 93U5P1qHPXAhXhiw1T15z3f1cBqFw9fWrd3Yzz1nDk8b2aRbrrM 
Private Key Hex: F7DBD21285F621F1C7A47AE7F63D06C276FE49839F4842DDEF805477936812A5

quiero usar el 6.49689241 btc de la transacción anterior (testnet)

912a2c3d84c8572b39c173b2bcde950cfe4ae07756bac189ace98f198d5ccb7d

y envíe (como prueba) algunos de vuelta al grifo mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf

Primero, para construir la transacción sin procesar sin firmar, agrego estos bytes:

01000000 (version number)
01 (number of inputs)
7dcb5c8d198fe9ac89c1ba5677e04afe0c95debcb273c1392b57c8843d2c2a91 (reversed previous tx hash)
01000000 (output index)
1976a9142de490b09ef14673af2bb4998fcb9f6b8446a84e88ac (previous tx script with its length at start) 
ffffffff (sequence)
02 (number of outputs)
496ff50200000000 (first output little endian amount. The faucet)
1976a914ac19d3fd17710e6b9a331022fe92c693fdf6659588ac (first output script with its length at start)
0046c32300000000 (second output little endian amount. My address, for change)
1976a9142de490b09ef14673af2bb4998fcb9f6b8446a84e88ac  (second script with its length at start)
00000000 (locktime)
01000000 (hash code type)

El resultado es

01000000017dcb5c8d198fe9ac89c1ba5677e04afe0c95debcb273c1392b57c8843d2c2a91010000001976a9142de490b09ef14673af2bb4998fcb9f6b8446a84e88acffffffff02496ff502000000001976a914ac19d3fd17710e6b9a331022fe92c693fdf6659588ac0046c323000000001976a9142de490b09ef14673af2bb4998fcb9f6b8446a84e88ac0000000001000000

Luego recupero el hash de la transacción presionando la transacción sin procesar en una función SHA256 doble que me devuelve

f64b6480a2888596636d4995153e990ce95582a1308c9c568d2698e6dc1f7893

Luego, firmo con la clave privada hexadecimal (prvkeyHex) el hash de la transacción (txHash) con esta función:

X9ECParameters curve = SecNamedCurves.GetByName("secp256k1");
ECDomainParameters dom = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H)
ECKeyParameters params = new ECPrivateKeyParameters(new BigInteger(1, prvkeyHex), dom);
ECDsaSigner signer = new ECDsaSigner();
signer.Init(true, params);
BigInteger[] sig = signer.GenerateSignature(txHash);
MemoryStream ms = new MemoryStream(72);
DerSequenceGenerator seq = new DerSequenceGenerator(ms);
seq.AddObject(new DerInteger(sig[0]));
seq.AddObject(new DerInteger(sig[1]));
seq.Close();
byte[] signature = ms.ToArray();

Lo que me devuelve una matriz de bytes de una longitud máxima de 72 bytes a partir de 30. En mi última prueba:

3045022100abceff62d3192b686c405d10516ff0e6f9ff221c00284d766200a6abb42361be02202972970369d6b9308467e15ebafd3f6b9faf111886071e3c429b34e9407e8d23

Luego construyo mi scriptSig final con la clave pública hexadecimal y sus longitudes que resultan como

483045022100abceff62d3192b686c405d10516ff0e6f9ff221c00284d766200a6abb42361be02202972970369d6b9308467e15ebafd3f6b9faf111886071e3c429b34e9407e8d2301410482052ef9560585ed62f046ee45c1b5f85448bcf1bd5ce36a7d35eb00c8a146c14bf99223907f9a8688e6f84b54fd747a637bb82f02e296203e735e7a6b40059f

Finalmente construí mi transacción sin procesar firmada:

01000000 (version number)
01 (number of inputs)
7dcb5c8d198fe9ac89c1ba5677e04afe0c95debcb273c1392b57c8843d2c2a91 (reversed previous tx hash)
01000000 (output index)
8b (scriptSig length)
483045022100abceff62d3192b686c405d10516ff0e6f9ff221c00284d766200a6abb42361be02202972970369d6b9308467e15ebafd3f6b9faf111886071e3c429b34e9407e8d2301410482052ef9560585ed62f046ee45c1b5f85448bcf1bd5ce36a7d35eb00c8a146c14bf99223907f9a8688e6f84b54fd747a637bb82f02e296203e735e7a6b40059f (scriptSig)
ffffffff (sequence)
02 (number of outputs)
496ff50200000000 (first output little endian amount. The faucet)
1976a914ac19d3fd17710e6b9a331022fe92c693fdf6659588ac (first output script with its length at start)
0046c32300000000 (second output little endian amount. My address, for change)
1976a9142de490b09ef14673af2bb4998fcb9f6b8446a84e88ac  (second script with its length at start)
00000000 (locktime)

El resultado final me da esa transacción sin procesar firmada

01000000017dcb5c8d198fe9ac89c1ba5677e04afe0c95debcb273c1392b57c8843d2c2a91010000008b483045022100abceff62d3192b686c405d10516ff0e6f9ff221c00284d766200a6abb42361be02202972970369d6b9308467e15ebafd3f6b9faf111886071e3c429b34e9407e8d2301410482052ef9560585ed62f046ee45c1b5f85448bcf1bd5ce36a7d35eb00c8a146c14bf99223907f9a8688e6f84b54fd747a637bb82f02e296203e735e7a6b40059fffffffff02496ff502000000001976a914ac19d3fd17710e6b9a331022fe92c693fdf6659588ac0046c323000000001976a9142de490b09ef14673af2bb4998fcb9f6b8446a84e88ac00000000

Pero ahora, cuando trato de enviar esa transacción sin procesar firmada en la red de testnet mediante un servicio web, cada vez que recibo un error. sandbox.smartbit.com.au/txs/pushtx devuélveme

"PUSH TRANSACTION ERROR: 16: MANDATORY-SCRIPT-VERIFY-FLAG-FAILED (SCRIPT EVALUATED WITHOUT ERROR BUT FINISHED WITH A FALSE/EMPTY TOP STACK ELEMENT)"

live.blockcypher.com/btc-testnet/pushtx/ devuélveme

"Error sending transaction: Error running script for input 0 referencing 912a2c3d84c8572b39c173b2bcde950cfe4ae07756bac189ace98f198d5ccb7d at 1: Script was NOT verified successfully.."

lo mismo con tbtc.blockr.io/tx/push

Estoy lejos de dominar bitcoin o c#, estoy aprendiendo (y quiero aprender cómo funciona). ¿Alguien ve dónde está mi error? Gracias

Respuestas (2)

La firma que produjo es correcta pero por un "mensaje" incorrecto, que es el único error que pude encontrar. Esto es lo que firmas:

f64b6480a2888596636d4995153e990ce95582a1308c9c568d2698e6dc1f7893

Lo cual está mal (ya que invertiste el resultado). En su lugar, debería haber firmado el resultado real que obtiene de su SHA256(SHA256(<bytes>))función de firma, es decir, esto en su lugar:

93781fdce698268d569c8c30a18255e90c993e1595496d63968588a280644bf6

Hay un par de lugares donde invertimos el resultado del hash y este no es uno de ellos. Aquí simplemente está codificando la transacción serializada para cambiar el tamaño de la misma de una longitud arbitraria a un tamaño fijo de 32 bytes para que pueda firmarla utilizando el esquema ECDSA.

Existe un posible problema adicional con el uso directo de BouncyCastle o cualquier biblioteca similar diseñada para criptografía en general, no para bitcoin, donde la firma que devuelven puede tener un svalor (el segundo BigIntegeren su firma) que es más grande que la curva N/2. En cuyo caso tendrás que cambiarlo a s'calculando s' = N - s.

¿Por qué no usa la biblioteca NBitcoin para crear una transacción sin procesar y publicarla en blockr.io o en cualquier proveedor de API que esté usando? NBitcoin es una gran biblioteca para desarrolladores de criptomonedas C#.