6

I'm trying to sign (using secp256k1) a transaction as explained here. I'm using my own private key and an unsigned transaction as the input.

I'm able to sign it using the JSON-RPC client, but when I sign it with my own ruby script and try to submit it using blockchain.info/pushTx it returns "Invalid signature".

@secret = PRIVATE_KEY_IN_HEX
@keypair_array = Bitcoin::OpenSSL_EC.regenerate_key(@secret)
@pubkey =  @keypair_array[1]

@keypair = OpenSSL::PKey::EC.new("secp256k1")
@keypair.private_key = @keypair_array[0].hex
@keypair.public_key = ::OpenSSL::PKey::EC::Point.from_hex(@keypair.group, @pubkey)

sha_little_endian = SHA_256_HEX_STRING.rjust(64,"0").scan(/(..)/).reverse.join()
signature_binary = @keypair.dsa_sign_asn1([sha_little_endian].pack("H*"))

# Alternative (as in bitcoin-ruby)
# signature_binary = @keypair.dsa_sign_asn1([SHA_256_HEX_STRING].pack("H*"))

signature = signature_binary.unpack("H*").first

First I take the SHA hash, e.g. ABCD...001124 and convert it from big endian to little endian: 241100....CDAB. I do this because in the BitcoinQT client the hash is represented as an uint256 which is cast to a char array used as an input for signing. The result is just stored as a var so I assume I don't have to reverse it.

You'll need to download this OpenSSL_EC file and add the following code to reproduce the above steps.

require 'openssl'
require 'ffi'

module Bitcoin
  def self.require_dependency(dummy1, dummy2)
  end  
autoload :OpenSSL_EC, "./openssl"
end  

module ::OpenSSL
  class BN
    def self.from_hex(hex); new(hex, 16); end
    end
  class PKey::EC::Point
    def self.from_hex(group, hex)
      new(group, BN.from_hex(hex))
    end
  end
end

What am I missing?

  • Armory can be used to manually sign and verify messages, that should prove a useful debugging tool. In addition BitcoinQT client can be configured to run a JSON-RPC server, which allows you to verify messages using the verifymessage method. – Sjors Provoost May 11 '13 at 12:28
  • Experimenting with Armory I learned that unlike sha-256 hashes, every time you generate a secp256k1 signature it's different. I also found that the signature generated by my ruby code was not valid as far as Armory was concerned, even though the validate method in ruby did believe it to be correct. That strongly suggests an encoding issue. – Sjors Provoost May 11 '13 at 12:59
  • Actually, message verification is not the same as transaction verification. So don't use Armory or the verifymessage for this! Instead, rely on signrawtransaction. Unfortunately, I'm having issues with that as well. – Sjors Provoost May 11 '13 at 16:06
  • I'm able to sign my own real transaction using signrawtransaction. The one in the example throws an invalid key error, but I can live with that. – Sjors Provoost May 11 '13 at 19:57
  • I studied the original C code of the Bitcoin client. It stores the digest as a uint256, which is cast to a char before signing. The signature itself is a char and doesn't get cast to anything else as far as I can see. I modified my question to reflect that. – Sjors Provoost May 13 '13 at 17:28
1

The binary encoding of the signature cannot be used to determine if you've performed the steps correctly - it will differ for each signature (even with the same private key).

So the signature of a hash signed with the same private key will be different every time. A random number is included in the formula that calculates the signature, so the resulting signature will be different each time. If a random number was not used in the formula, signing two different hashes with the same private key would make it possible to calculate the private key from the two signatures (which is the reason the security of the Sony PlayStation 3 was breached). You can read more about that here: http://www.johannes-bauer.com/compsci/ecc/

If you want to test whether you've made a valid transaction, then feed it to bitcoind using the sendrawtransaction command. It will spit out an error if the transaction is invalid, and if it's valid you will see the transaction going through.

  • Thanks. I modified my question to take into account that the signature is never the same. Whenever I submit the transaction I get an "Invalid Signature" error. As soon as that stops, I'll have fixed it... By reading the C source code I also learned that the hash is a uint256 which is cast to a char for signing. I think I compensated for that, but it still doesn't work. – Sjors Provoost May 13 '13 at 17:27
1

Done! Here's the resulting script. Although I recommend you use bitcoin-ruby for anything serious.

There's a couple of things that went wrong.

I introduced a new bug during debugging: I commented out the hash_code_type field in the pre-signed transaction, which I needed to do in order to sign it with the JSON-RPC client. I forgot to undo that, so I was signing the wrong hash half the time. This became clear once I used bitcoin-ruby to create the same transaction and ask it what hash it was about to sign, something you can't (easily) do with the JSON-RPC client.

The little_endian bit is not needed, although I'm still a bit confused as to why not. The correct line is:

signature_binary = @keypair.dsa_sign_asn1([SHA_256_HEX_STRING].pack("H*"))

In other words, good debugging tools and methodology are really important.

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.