Skip to content

Instantly share code, notes, and snippets.

@rllola
Last active August 30, 2023 12:07
Show Gist options
  • Save rllola/3a6be4222c7b52f934c28843e7b965aa to your computer and use it in GitHub Desktop.
Save rllola/3a6be4222c7b52f934c28843e7b965aa to your computer and use it in GitHub Desktop.
Attempting to verify the signature for a single signing flag transaction where the ouput is non existant
import ecdsa
# https://bitcoin.stackexchange.com/questions/114850/sighash-single-with-no-corresponding-output
pubkey = "044edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c108172c4776865f02047b39cd704135c00c1b00085e0d1b9255405ac7079fa50a2"
signature = "912f994094193109a9faedf7ef855220638f95ac51c66d4eb46740dd1c0813fa100bc99adb8b64fb784173ca8883a78835e156b74f143c02e071dc82695e8472"
msg = bytes.fromhex("0100000000000000000000000000000000000000000000000000000000000000")
verify_key = ecdsa.VerifyingKey.from_string(bytearray.fromhex(pubkey), curve=ecdsa.SECP256k1)
# To verify a digest we need to call a special function because `hashfunc=None` will use the default hashfunc on the msg
# see https://github.com/tlsfuzzer/python-ecdsa/blob/5db1d2d2c415b25e741c06451dbc72cb44f75e23/src/ecdsa/keys.py#L671
# verify_key.verify(bytearray.fromhex(signature), msg, hashfunc=None)
verify_key.verify_digest(bytes.fromhex(signature), msg)
@pbies
Copy link

pbies commented Aug 4, 2023

Any progress with that?

@rllola
Copy link
Author

rllola commented Aug 5, 2023

Unfortunately no

@pbies
Copy link

pbies commented Aug 5, 2023

@rllola
Copy link
Author

rllola commented Aug 25, 2023

I have solved it!

The problem was that I used the wrong function to verify the signature with the ecdsa lib. Using hashfunc=None doesn't not hash the msg it just replace it with the default one. See https://github.com/tlsfuzzer/python-ecdsa/blob/5db1d2d2c415b25e741c06451dbc72cb44f75e23/src/ecdsa/keys.py#L671

0100000000000000000000000000000000000000000000000000000000000000 being is double hash value already we don't want it to be hashed so using verify_digest skipped this step and just verify the signature.

@pbies
Copy link

pbies commented Aug 25, 2023

Yes, you are right!

The remaining problem is if cryptocurrency software will accept it.

@rllola
Copy link
Author

rllola commented Aug 25, 2023

It will be accepted by the Bitcoin software. It is a known bug in Bitcoin actually. I can try to explain it.

In the case of sighash single transaction you need to have the number of outputs matching the number of inputs in your transaction. A sighash single transaction without a matching output should not have been considered correct. In the Bitcoin code, it does return an error code 01. However what happened is that in the code they forgot to check for error code returned by the function and considered 01 to be the message to sign...

01 padded to match 32 bytes is 0100000000000000000000000000000000000000000000000000000000000000. And that the message that was signed by mistake. To keep consensus it has stayed like this.

The original function in script.cpp

uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType)
{
    if (nIn >= txTo.vin.size())
    {
        printf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn);
        return 1;
    }

@pbies
Copy link

pbies commented Aug 28, 2023

Take a look here:

https://github.com/MatanHamilis/bitcoin-sighash-steal/

  • nice Rust program.

@pbies
Copy link

pbies commented Aug 29, 2023

So what should be given to input[0] and [1] as script_sig? Pubkey or HASH_ONE? Let's say for [0] I have my own private key to sign, but what about input[1]?

@rllola
Copy link
Author

rllola commented Aug 30, 2023

It depends what are the previous output. Are you trying to build the message to sign ?

@pbies
Copy link

pbies commented Aug 30, 2023

I am trying to successfully broadcast a valid transaction (tx). One is mine previous output, second is other output. I am struggling with properly signing the second output while using SIGHASH_SINGLE. For now there lands second output public key, which is not accepted. I suspect there should be HASH_ONE which you have already mentioned - the 0100000000000000000000000000000000000000000000000000000000000000.

@rllola
Copy link
Author

rllola commented Aug 30, 2023

You should never sign the HASH_ONE. It is a bug and people could steal your funds 😅

How many inputs you have and how many ouputs ? If one of the input is not yours you should get the person it belongs to to sign it.

@pbies
Copy link

pbies commented Aug 30, 2023

You should never sign the HASH_ONE. It is a bug and people could steal your funds 😅

How many inputs you have and how many ouputs ? If one of the input is not yours you should get the person it belongs to to sign it.

Yes, that's what I am trying to verify - if SIGHASH_SINGLE works.

I have two inputs and one output. He would sign it but we are trying to do the SIGHASH_SINGLE so his signing should be not needed. However I cannot make it work.

@rllola
Copy link
Author

rllola commented Aug 30, 2023

You still need the other person to sign the HASH_ONE value. Then people can re-use this signature to steal what funds are left on the address. The signature can be reuse because it sign the simple value 0100000000000000000000000000000000000000000000000000000000000000 (aka HASH_ONE) and not a real unique transaction.

@pbies
Copy link

pbies commented Aug 30, 2023

You still need the other person to sign the HASH_ONE value. Then people can re-use this signature to steal what funds are left on the address. The signature can be reuse because it sign the simple value 0100000000000000000000000000000000000000000000000000000000000000 (aka HASH_ONE) and not a real unique transaction.

Ah! You're right! Thank you for explanation! I was missing that info wondering if it is possible to adapt the code. Now I know it is impossible.

@pbies
Copy link

pbies commented Aug 30, 2023

Is HASH_ONE hashed/signed always the same?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment