ofek/coincurve

ECDSA signature verification fails when the x contains too many trailing 0s

Closed this issue · 1 comments

coincurve 13.0.0 fails when the x coordinate contain too many trailing 0s. You can check this
with the following teste vectors from Google Wycheproof:

{
  "algorithm" : "ECDSA",
  "generatorVersion" : "0.8r12",
  "numberOfTests" : 380,
  "header" : [
    "Test vectors of type EcdsaVerify are meant for the verification",
    "of ASN encoded ECDSA signatures."
  ],
  "notes" : {
    "BER" : "This is a signature with correct values for (r, s) but using some alternative BER encoding instead of DER encoding. Implementations should not accept such signatures to limit signature malleability.",
    "EdgeCase" : "Edge case values such as r=1 and s=0 can lead to forgeries if the ECDSA implementation does not check boundaries and computes s^(-1)==0.",
    "MissingZero" : "Some implementations of ECDSA and DSA incorrectly encode r and s by not including leading zeros in the ASN encoding of integers when necessary. Hence, some implementations (e.g. jdk) allow signatures with incorrect ASN encodings assuming that the signature is otherwise valid.",
    "PointDuplication" : "Some implementations of ECDSA do not handle duplication and points at infinity correctly. This is a test vector that has been specially crafted to check for such an omission."
  },
  "schema" : "ecdsa_verify_schema.json",
  "testGroups" : [
    {
      "key" : {
        "curve" : "secp256k1",
        "keySize" : 256,
        "type" : "EcPublicKey",
        "uncompressed" : "046d4a7f60d4774a4f0aa8bbdedb953c7eea7909407e3164755664bc2800000000e659d34e4df38d9e8c9eaadfba36612c769195be86c77aac3f36e78b538680fb",
        "wx" : "6d4a7f60d4774a4f0aa8bbdedb953c7eea7909407e3164755664bc2800000000",
        "wy" : "00e659d34e4df38d9e8c9eaadfba36612c769195be86c77aac3f36e78b538680fb"
      },
      "keyDer" : "3056301006072a8648ce3d020106052b8104000a034200046d4a7f60d4774a4f0aa8bbdedb953c7eea7909407e3164755664bc2800000000e659d34e4df38d9e8c9eaadfba36612c769195be86c77aac3f36e78b538680fb",
      "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEbUp/YNR3Sk8KqLve25U8fup5CUB+MWR1\nVmS8KAAAAADmWdNOTfONnoyeqt+6NmEsdpGVvobHeqw/NueLU4aA+w==\n-----END PUBLIC KEY-----",
      "sha" : "SHA-256",
      "type" : "EcdsaVerify",
      "tests" : [
        {
          "tcId" : 378,
          "comment" : "x-coordinate of the public key has many trailing 0's",
          "msg" : "4d657373616765",
          "sig" : "30450220176a2557566ffa518b11226694eb9802ed2098bfe278e5570fe1d5d7af18a943022100ed6e2095f12a03f2eaf6718f430ec5fe2829fd1646ab648701656fd31221b97d",
          "result" : "valid",
          "flags" : []
        },
        {
          "tcId" : 379,
          "comment" : "x-coordinate of the public key has many trailing 0's",
          "msg" : "4d657373616765",
          "sig" : "3045022060be20c3dbc162dd34d26780621c104bbe5dace630171b2daef0d826409ee5c2022100bd8081b27762ab6e8f425956bf604e332fa066a99b59f87e27dc1198b26f5caa",
          "result" : "valid",
          "flags" : []
        },
        {
          "tcId" : 380,
          "comment" : "x-coordinate of the public key has many trailing 0's",
          "msg" : "4d657373616765",
          "sig" : "3046022100edf03cf63f658883289a1a593d1007895b9f236d27c9c1f1313089aaed6b16ae022100e5b22903f7eb23adc2e01057e39b0408d495f694c83f306f1216c9bf87506074",
          "result" : "valid",
          "flags" : []
        }
      ]
    }
  ]
}

and proof of concept:

from coincurve.keys import PrivateKey, PublicKey
from coincurve.utils import bytes_to_int, int_to_bytes_padded, verify_signature

from cryptography import exceptions
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import utils

from hashlib import sha256


import json


def main():

    with open("ecdsa_secp256k1_sha256_test_x_coordinate_trail_0.json", "r") as read_file:
        secp256k1_sha256_test = json.load(read_file)

    for test_group in secp256k1_sha256_test["testGroups"]:
        comp_str = test_group["key"]["uncompressed"]
        co_x = test_group["key"]["wx"]
        co_y = test_group["key"]["wy"]

        public_key = PublicKey(bytes.fromhex(comp_str))

    
        for test in test_group["tests"]:
            print("Loading test", test["tcId"], "with result:", test["result"], "\n")

            ### using python coincurve

            signature = bytes.fromhex(test["sig"])
            message = bytes.fromhex(test["msg"])
            print("\tcoincurve: ", public_key.verify(signature, message))

            ### using python cryptography 

            curve = ec.SECP256K1()
            algo = ec.ECDSA(hashes.SHA256())

            pubnum = ec.EllipticCurvePublicNumbers(int(co_x, 16), int(co_y, 16), curve)
            
            data = bytes(bytearray.fromhex(test["msg"]))
            public_key_2 = pubnum.public_key(default_backend())
            signature = bytes(bytearray.fromhex(test["sig"]))
            
            try:
                public_key_2.verify(signature, data, ec.ECDSA(hashes.SHA256()))

            except exceptions.InvalidSignature:
                print("\tcryptography.io: False")
            else:
                print("\tcryptography.io: True")
  
if __name__ == "__main__":
    main()

where I compare the output of coincurve with the python cryptography
library.