Hey everyone.
I’m having trouble verifying the signature in this process. When I calculate the Z value, it seems like the Kx value of point K (which is R) doesn’t check out. Am I messing up the message calculation or something? Also, the verification process seems pretty tricky.
You might notice that there are repeated S and R values in the signatures.
Check out this transaction: https://www.blockchain.com/explorer/transactions/btc/bd17bfb7051bef5ca916931f06493a0af0db080b4df21ee9768fa0f3d775070a
Does anyone have a clue about how to do the signature verification mathematically for this transaction?
Thanks!
The signature verification process is the same for all transactions (TX). If you want a comprehensive explanation, I recommend checking out this detailed resource: https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages
But in short you need to calculate the Proof of signature s
In short, the proof of signature involves verifying the mathematical relationship between the transaction message, the public key, and the provided signature. Here's a simplified breakdown of the process:
Calculate z is the double SHA-256 hash of the transaction message.
Use the nonce k is a randomly generated ephemeral private key that ensures uniqueness for each signature.
There is a z = double sha256 of the message
There is a Nonce K (like another ephemeral private key)
Compute R and extract r:
R = k * G (where G is the curve's generator point).
Take the x-coordinate of R to get r.
Calculate s (the proof of signature)
s= k^-1 * (z + r * privatekey) mod N
It's important to note that in cryptography, we use s (the computed part of the signature) to ensure the validity of the private key's influence on the signed message. Occasionally, you'll see -s (negative s) in verification contexts. This happens because ECDSA signatures are valid in both positive and negative forms due to the symmetry of elliptic curves. Verification software typically normalizes the signature by ensuring that s is the smaller of the two possible values (s or -s mod N)
The critical takeaway is that the process guarantees that only the holder of the correct private key could have generated the signature, ensuring authenticity. If you'd like me to expand on any specific step or provide further details, feel free to ask!
Edit, NOW i got you that is really a weird case not only R is duplicated it is also S... WTF how are those signatures valid, even if all of them has different Z value?
Hello Albert
Thank you for the valuable information. I will review it again. I have been familiar with this information for a long time. I would like to explain it through a signature example from the above Operations. I will share sagemath log for this.
As you can see, the signature is not verified with these values, I tried both manually and with various scripts to see if I calculated the Z value incorrectly. also the signature type is pubkeyhash
This value of R_int
(53263660719217912605226701691521450355210418686441457516777995068762774452686)
The x value of this ECpoint must be the same.
(19829164371494234377691210117539544104487361239120201118967338701656141495058 : 32604581094205453339388830956426246807569685087240405477122130035957072641097 : 1)
Well that is the point of this topic, according to the math having the same same R and S is only possible if the hash is the same for all the message as long they belong to the same publickey.
I already checked the Signaures and all of them has the same R and S But the values of each those utxo are different. This only should mean that such Transaction is invalid.
Yes.
All inputs, together with those with the same R and S are signed with [SINGLE|ANYONECANPAY] (0x83) sighash flag. (can also be the reason for the same R,S)
But you've calculated the message hash for SIGHASH_ALL (0x01).
Problem is, I cannot find a single "RSZ tool" that can check for the sighash_flag then switch the z-value calculation algorithm accordingly.
With that; DYOR, I'll just leave you some references:
https://static.learnmeabitcoin.com/diagrams/png/keys-signature-hash-types.pnghttps://raghavsood.com/blog/2018/06/10/bitcoin-signature-types-sighash/
Hello, thank you very much for your explanation.
I hope I can verify manually.
I am trying to understand validation thoroughly.
I have separated this process as an example.
https://blockchain.info/rawtx/bd17bfb7051bef5ca916931f06493a0af0db080b4df21ee9768fa0f3d775070a?format=hex
It can be considered as a challenge open to members who have time and interest. I really want to understand the algorithm of verification.
I hope it will help those who want to work for verification.
Thank you very much.
Hello ,
I think I solved the this problem. the problem is SIGHASH_SINGLE bug
after the first signature, all other signatures all have a hash value of 0100000000000000000000000000000000000000000000000000000000000000. i tried it and was able to verify.
It is not a bug, that is the only way SIGHASH_SINGLE can be defined. That sighash flag says you have to sign output at the same index as your input but in Bitcoin protocol there is no rule saying the transaction has to have same number of inputs and outputs meaning there may not be any output at the same index but you still have to sign something if that sighash is used, therefore a fixed value is used.
It is definitely a bug. Normally, the whole transaction should be just marked as invalid.
Not really, because if you compare for example legacy with Segwit, then it is defined differently. And for "bc1q" address, you won't have just the SHA-256 of "1", but you would have more complex way of computing transaction hash instead.
Not to mention, that there are many other ways to define it, because if somebody would really mean "z=1", then the endianness should be reversed.
It would be much better, if that specific case would be just marked as invalid, and rejected. Also because if you use "z=endian(1)", then you can move coins, where the ECDSA signature would be valid, but nobody would know the private key. For example: https://mempool.space/testnet/tx/3952b35bde53eb3f4871824f0b6b8c5ad25ca84ce83f04eb1c1d69b83ad6e448
Here, you have this innocently looking public key: https://mempool.space/testnet/address/032baf163f5e27261ab3228e61fb86dc98054abd514751fce93d7444e8fbc6a293
As you can see, those coins were successfully moved in testnet. And in general, if you start picking different "(r,s)" signatures, then you can use public key recovery, and move coins, even if you don't know the private key. Fortunately, this "z-value" is just "endian(1)", and not something weaker, because then, it could be possible to exploit it, and move someone else's coins, for example by using "r=target_public_key".
It would only be a bug if the code were unintentionally signing 1, but it can't be a bug when the developer knowingly wrote that in the code to intentionally sign 1 in that particular scenario.
If we do that we would be enforcing an unreasonable rule to force any transaction using SIGHASH_SINGLE to have same number or more outputs than inputs. If the user doesn't want to sign 1 then they can change their tx and if they want to sign 1 they have that option.
The current way can come in handy in a multisig scenario that also provides security (eg. in 2-of-2 where 1 cosigner uses SIGHASH_SINGLE and the other uses SIGHASH_ALL).
Fair point but that wasn't what I was talking about.
Even in SegWit when we sign outputs we still have to have a corresponding output to sign with SIGHASH_SINGLE and if there is none we still sign a fixed value. So in that regard that is the only way that it can be defined. To be specific in SegWit both hashSequence and hashOutputs are going to be zero in our scenario.
No you can't. If you could do that, then everyone would have just gone around and spend every single coin in any P2PK output that's on chain.
The example you posted is a special case created with a special public key that would produce a valid signature with (r,s,z)=(1,1,1).
If you are the one creating that public key then you can no longer claim the "key is unknown" when you created the signature as well. Have you noticed that the source and destination of the coins were the same?
From tb1qft...h7jw to that pubkey then from that pubkey to tb1qft...h7jw. Meaning the same person who intentionally created that special public key also spent the coins.
ECDSA is just math, you can find weird ways of producing weird but valid signatures as long as you control all the variables in it. But if you don't, like when you don't have the private key or when you are not the one creating the public key (that received the coins) assuming the key is randomly generated, you won't be able to produce a valid signature to spend those coins regardless of what the sighash is.
Looks like a bug. This "printf" doesn't look like "let's put 1 intentionally", but rather "let's report an error, and mark it as invalid". However, in this specific case, the whole function was supposed to return z-value, so when it returned "1", the rest of the code was executed normally, and treated it like every other value.
It is unknown. The public key is known, but the private key is completely unknown. If anyone could get a private key out of it, then it would be possible to use the same method, and get a private key for any other public key as well.
Here, nobody knows d-value or k-value, which means that nobody knows the private key. Because if it would be known, then people could just use "R=some_public_key", solve it, and get a private key out of it.
Also, the same tricks cannot be replicated on Taproot, because it was designed to block public key recovery, so you cannot get a matching public key, out of some signature.
Yes, but it is just P2WSH for OP_TRUE. And it doesn't matter, if you want to test out of bounds SIGHASH_SINGLE, the first input and output can be anything.