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

2 Answers 2

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
12
  • 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 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
    Commented Jun 17, 2021 at 20:00
  • 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. Commented Jun 17, 2021 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. Commented Jun 17, 2021 at 21:51
  • 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. Commented Jun 18, 2021 at 1:04
  • 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 bitcoin.stackexchange.com/questions/12554 and bitcoin.stackexchange.com/questions/90799 Commented Jun 18, 2021 at 5:12
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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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