lian / bitcoin-ruby

bitcoin utils and protocol in ruby.
Other
922 stars 322 forks source link

How to get a bitcoin cash transaction signed with bitcoin-ruby gem? #259

Closed hanshaueter closed 6 years ago

hanshaueter commented 6 years ago

When I try to send the transaction via https://blockdozer.com/tx/send I receive the following error message: "16: mandatory-script-verify-flag-failed (Script failed an OP_EQUALVERIFY operation). Code:-26". I used hashtype 0 and fork_id 64 as I understand that's what needs to be changed from bitcoin to bitcoin cash. => sighash = new_tx.signature_hash_for_input(0, prev_tx, 0, prev_tx.out, 64) I guess I'm missing another relevant adjustment. Please find below how I built the transaction. Any input would be highly appreciated. new_tx = build_tx do |t|

t.input do |i| i.prev_out prev_tx.hash i.prev_out_index prev_out_index i.signature_key key end

t.output do |o| o.value 9000 o.script {|s| s.recipient "1BLe5eavCg6Jxkt21edCxgPKmY3Ks8hTGL" } end

end sighash = new_tx.signature_hash_for_input(0, prev_tx, 0, prev_tx.out, 64) sig = Bitcoin.sign_data(key.key, sighash)

public_key = regenerate_public_key(key.key.private_key_hex) new_tx.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [public_key].pack("H*"), 0)

new_tx = Bitcoin::Protocol::Tx.new( new_tx.to_payload ) p new_tx.to_payload.unpack("H*")[0]

azuchi commented 6 years ago

maybe regenerate_public_key returns uncompressed pubkey, so pubkey hash check failed.

hanshaueter commented 6 years ago

Thank you very much for your kind support which I have tried to implement. I tried to generate a Bitcoin Cash transaction with a compressed key (or at least that's what I think I was using) and the transaction still doesn't go through. It seems that the binary transaction is 2bits too short.
I was using the Bitcoin.open_key method this time. Please find below the full details of the code: `prev_tx_output_index = 1 prev_tx_json = HTTParty.get('https://blockdozer.com/insight-api/rawtx/4cda2581911ab3d7ee29b22dbd7701f278d33cdcd8535d911ab621a96171af24')["rawtx"].to_s prev_tx_test =[prev_tx_json].pack("H*") prev_tx = Bitcoin::Protocol::Tx.new(prev_tx_test) value = prev_tx.outputs[prev_tx_output_index].value - 1000

tx = Bitcoin::Protocol::Tx.new tx.add_in Bitcoin::Protocol::TxIn.new(prev_tx.binary_hash, prev_tx_output_index, 0) tx.add_out Bitcoin::Protocol::TxOut.value_to_address(value, "1BLe5eavCg6Jxkt21edCxgPKmY3Ks8hTGL")

key = Bitcoin.open_key("c35ec65e83188458487e127db7a554aa5e3567ff0e131c7e5a94a5a1c7f54a18")

sig = Bitcoin.sign_data(key, tx.signature_hash_for_input(0, prev_tx.out[1].pk_script, 0, prev_tx.out[1], 0)) tx.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [key.public_key_hex].pack("H*"), 0) tx = Bitcoin::Protocol::Tx.new( tx.to_payload )

p tx.verify_input_signature(0, prev_tx) == true puts tx.to_payload.unpack("H*")[0]

Any support would be highly appreciated.

lian commented 6 years ago

use the https://github.com/lian/bitcoin-ruby#keysaddresses Bitcoin::Key object instead. gets rid of compressed vs uncompressed. also Key#initialize(privkey = nil, pubkey = nil, opts={compressed: true}) can define if the used key is compressed or uncompressed

hanshaueter commented 6 years ago

Thank you for your inputs! Which is very helpful! Now I managed to get the signature test tx.verify_input_signature(0, prev_tx) on true. I changed the following elements: key = Bitcoin::Key.new("c35ec65e83188458487e127db7a554aa5e3567ff0e131c7e5a94a5a1c7f54a18", pubkey = nil, opts={compressed: true}) sig = Bitcoin.sign_data(key.key, tx.signature_hash_for_input(0, prev_tx.out[1].pk_script, 0, prev_tx.out[1], 0)) tx.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [key.pub].pack("H*"), 0)

However, when I go on https://blockdozer.com to send the transaction, I still get the "An error occured: 16: mandatory-script-verify-flag-failed (Signature hash type missing or not understood). Code:-26" Do you have an idea why this happens? It seems there is still a problem with the signature. Thank you very much for inputs!

lian commented 6 years ago

signature_hash_for_input(input_idx, subscript, hash_type=nil, prev_out_value=nil, fork_id=nil) you set fork_id to 0 in your code so it won't run signature_hash_for_input_bip143 internally, set it to 64 instead. also your hash_type should be hash_type = Bitcoin::Script::SIGHASH_TYPE[:all] | Bitcoin::Script::SIGHASH_TYPE[:forkid] so replace those 0s with that value instead too

hanshaueter commented 6 years ago

Thank you very much. I have changed the two lines as follows: sig = Bitcoin.sign_data(key.key, tx.signature_hash_for_input(0, prev_tx.out[1].pk_script, 1, prev_tx.out[1], 64)) tx.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [key.pub].pack("H*"), 1) I set the hash_type to 1 and fork_id to 64. I still get the same error ("An error occured: 16: mandatory-script-verify-flag-failed (Signature hash type missing or not understood). Code:-26") when submitting on blockdozer.com. I'm sorry I might have misunderstood your comment, but I can't get it running. If I set both hash_type and fork_id to 64: sig = Bitcoin.sign_data(key.key, tx.signature_hash_for_input(0, prev_tx.out[1].pk_script, 64, prev_tx.out[1], 64)) tx.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [key.pub].pack("H*"), 64) it get an error message in the Bash console "tx.rb:191:in `signature_hash_for_input': fork_id must be 0" Thank you very much for help. It's highly appreciated.

lian commented 6 years ago

i said to set hash_type to :all | :forkid which is 1 | 64 which is the value 65

just set and use those variables..

hash_type = Bitcoin::Script::SIGHASH_TYPE[:all] | Bitcoin::Script::SIGHASH_TYPE[:forkid]
forkid = Bitcoin::Script::SIGHASH_TYPE[:forkid]
hanshaueter commented 6 years ago

Your the best! I was able to successfully broadcast a transaction using your method! Thank you 1000 times for your inputs. I changed it as follows: `sig = Bitcoin.sign_data(key.key, tx.signature_hash_for_input(0, prev_tx.out[1].pk_script, 65, prev_tx.out[1].value, 0))

tx.in[0].script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [key.pub].pack("H*"), 65)`

I guess the only question I now still have is why does the test p tx.verify_input_signature(0, prev_tx) change to false?

lian commented 6 years ago

gotta pass the forkid too p tx.verify_input_signature(0, prev_tx, Time.now.to_i, { fork_id: Bitcoin::Script::SIGHASH_TYPE[:forkid] })

longhoangwkm commented 6 years ago

@lian https://github.com/lian/bitcoin-ruby/blob/master/lib/bitcoin/protocol/tx.rb#L191 As your said set fork_id to 0 in your code so it won't run signature_hash_for_input_bip143 internally, set it to 64 instead. i wonder how can we pass above code if fork_id = 64.