2

In BIP143 the first example has a witness signature and it doesn't appear to validate/be correct?! I wrote a super simple python program to demonstrate

import ecdsa
import asn1

#Importing the 3 pieces of data from the example to byte arrays pub = bytearray.fromhex("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357") sighash = bytearray.fromhex("c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670") dersigscript = bytearray.fromhex("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee")

#deocding the DEC encoding decoder = asn1.Decoder() decoder.start(bytes(dersigscript)) tag, sigscript = decoder.read()

#stripping off the script potion so we just have a signature sig = bytearray(sigscript)[2:66]

vk = ecdsa.VerifyingKey.from_string(pub, curve=ecdsa.SECP256k1) vk.verify(sig, sighash)

If anyone can shed some light on what stupid thing I am doing, or an example reference that breaks it down, so it works, that would be really helpful. I have also tried reversing byte order on all 8 combinations of the 3 inputs. That being said I have used the private and public key posted to do my own signatures/validation, so I am fairly confident they are in correct order, just less sure about the sig.

Thank you!!

----EDIT-----
Just wanted to list other things that were tried for completeness sake

  1. as pieter pointed out I was incorrectly decoding the private key. S and R are split up into 2 different variables
304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee
becomes
3044 02 20 3609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a 02 20 573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee
so the raw 64 byte sig is 609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee
unfortunately that still did not work

Additionally I tried using pythons native der style ecdsa which was still not successful from ecdsa.util import sigencode_der, sigdecode_der vk.verify(bytes(sig), bytes(sighash), sigdecode=sigdecode_der) and vk.verify(bytes(sig), bytes(sighash), hashlib.sha256, sigdecode=sigdecode_der) where sig starts with 0x30: 3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb

what is interesting is the python library didn't give me any errors except bad signature. So I can't seem to find any clues yet as to how to do this with a standard library. If I do figure it out I will post an answer

noone392
  • 422
  • 2
  • 12

2 Answers2

3

Using this library:

from cryptotools import Signature, PublicKey, hex_to_bytes

pub = PublicKey.from_hex('025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357') sig = Signature.from_hex('304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee') sighash = hex_to_bytes('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670')

>>> sig.verify_hash(sighash, pub) True

Mike D
  • 3,569
  • 1
  • 9
  • 19
  • Wait wait wit. Is the reason why I never see this done with a standard library because the bitcoin signature algorithm is not RFC compliant ?!?! From here https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm it says the signature is 71-73 bytes for the signature which is not correct. This is mind boggling – noone392 Jun 17 '21 at 20:00
  • @noone392 which RFC are you refering to? Afaik bitcoin signatures follow the DER encoding specification https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#der-encoding – Mike D Jun 17 '21 at 20:18
  • it is compatible with DER encoding, I mean the actual ECDSA specification like RFC 6979. the signature must be exactly 64 bytes to be SECP256k1 compliant. And the provided signature, while it matches the wiki on it, is not the correctly length to be compliant. Basically I don't think you can call this ECDSA rather its a bitcoin signature that uses elliptic curve math. – noone392 Jun 17 '21 at 20:25
  • Bitcoin uses ECDSA, with DER-encoded signatures. It follows those specifications exactly. ECDSA is a signature algorithm, not the encoding. – Pieter Wuille Jun 17 '21 at 20:45
  • @PieterWuille what specification are you referring to exactly? because in my example I DER decode it and use what I believe is the raw 64 byte key portion. – noone392 Jun 17 '21 at 20:57
  • 1
    Ok, your conversion from DER to 64-byte signature format is incorrect. A DER signature consists of a 2-byte "struct marker" (saying that a tuple follows), then a 2-byte "integer marker", followed by the R integer (variable number of bytes), then another 2-byte "integer marker", followed by the S integer (variable number of bytes). You need to extract the R and S elements, pad them to 32 bytes each (by prepending/removing zeroes up front), and then concatenate them. – Pieter Wuille Jun 17 '21 at 21:50
  • 1
    You can probably do that with the asn1 module by decoding the "sigscript" variable you get out (not really appropriately named, it's just a tuple of signature elements, no script involved) again with asn1. – Pieter Wuille Jun 17 '21 at 21:51
  • @PieterWuille Thank you for finding that issue. you help is very appreciated. For some reason I remember ecdsa sig being a single variable.... anyway I have the 2 32 byte pieces connected together now that I am parsing them. Unfortunately it still doesn't verify. I also tried using python's sigdecode=sigdecode_der to provide the der encoded format but also no luck... – noone392 Jun 18 '21 at 00:59
  • 1
    The problem is likely that you need to use VerifyingKey.verify_digest rather than .verify. The latter hashes the messages again, while you're already providing a hashed message. – Pieter Wuille Jun 18 '21 at 01:04
  • @PieterWuille That did it :-D I will post the answer. Thanks again – noone392 Jun 18 '21 at 01:45
  • 1
    The standard for ECDSA is X9.62, as clearly stated in RFC6979, and does not specify any encoding for the signature. RFC6979 in 2.4 step 4 notes, correctly, it is "common" to use DER-encoded ASN.1, but that's not a requirement, and it absolutely isn't fixed-length. The sample code in A.3 does DER, but that's only a sample. Bitcoin uses DER for transaction signatures, but the fixed-length 'plain' format for message signatures; see https://bitcoin.stackexchange.com/questions/12554/ and https://bitcoin.stackexchange.com/questions/90799/ – dave_thompson_085 Jun 18 '21 at 05:12
  • @dave_thompson_085 Thank you for the clarification – noone392 Jun 18 '21 at 15:55
2

ok So thanks to PieterWuille I (we) were able to figure out why my code wasn't working and how to use a standard python or openssl library.
These were the following problems

  1. the DER decoding for the python library was used incorrectly. We needed to iterate through all the variables and pull the two pieces of the signature (r and s) our separately and recombine them.
  2. Because the standard signing libraries have the hash built into the sign/verify, and bitcoin uses a double hash, to make it work with a standard library, you need to hash 1 time manually then provide it to the library to perform the second hash. This means we could not use sigHash as an input, but rather has to use the hash preImage. Posted below is about the shortest python script possible showing how to verify the signatures with the examples given in both raw format and DER encoded format:
import ecdsa
import asn1
import hashlib
#used for der version
from ecdsa.util import sigencode_der, sigdecode_der

#Importing the 3 pieces of data from the example to byte arrays pub = bytearray.fromhex("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357") DerEncodedSig = bytearray.fromhex("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee") prehashimage = bytearray.fromhex("0100000096b827c8483d4e9b96712b6713a7b68d6e8003a781feba36c31143470b4efd3752b0a642eea2fb7ae638c36f6252b6750293dbe574a806984b8e4d8548339a3bef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a010000001976a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac0046c32300000000ffffffff863ef3e1a92afbfdb97f31ad0fc7683ee943e9abcf2501590ff8f6551f47e5e51100000001000000")

#deocding the DEC encoding decoder = asn1.Decoder() decoder.start(bytes(DerEncodedSig)) tag, sigder = decoder.read()

sig1 = sigder[2:34] sig2 = sigder[36:68] sig = sig1+sig2

#Now we need to get the single sha256 hash of the preimage m = hashlib.sha256() m.update(prehashimage) singlesighash = m.digest()

#we won't use the double hash for our verify but this shows you it is the same as sigHash in bip143 n = hashlib.sha256() n.update(singlesighash) sigHash = n.digest

vk = ecdsa.VerifyingKey.from_string(bytes(pub), curve=ecdsa.SECP256k1) #we have to provide the hash lib because the default is not sha256 result = vk.verify(bytes(sig), bytes(singlesighash), hashlib.sha256) print(result) #this is the der native way and does not require us to der decode the signature but is compatible with openssl result = vk.verify(bytes(DerEncodedSig), bytes(singlesighash), hashlib.sha256, sigdecode=sigdecode_der) print(result)

noone392
  • 422
  • 2
  • 12