From e6b264f2959d60163a4ffe660391b8baa864dd28 Mon Sep 17 00:00:00 2001 From: mdxs Date: Sun, 7 Feb 2016 16:38:45 +0100 Subject: [PATCH] Support for SEC compressed keys --- ecdsa/keys.py | 44 ++++++++++++++++++++++++++++++++++++++++++- ecdsa/test_pyecdsa.py | 25 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/ecdsa/keys.py b/ecdsa/keys.py index 0b8a0d9b..bd5898ee 100644 --- a/ecdsa/keys.py +++ b/ecdsa/keys.py @@ -1,13 +1,15 @@ import binascii from . import ecdsa +from . import ellipticcurve from . import der from . import rfc6979 from .curves import NIST192p, find_curve +from .numbertheory import square_root_mod_prime +from .six import PY3, b from .util import string_to_number, number_to_string, randrange from .util import sigencode_string, sigdecode_string from .util import oid_ecPublicKey, encoded_oid_ecPublicKey -from .six import PY3, b from hashlib import sha1 @@ -78,6 +80,32 @@ def from_der(klass, string): assert point_str.startswith(b("\x00\x04")) return klass.from_string(point_str[2:], curve) + @classmethod + def from_sec(klass, string, curve=NIST192p, hashfunc=sha1, + validate_point=True): + """Convert a public key in SEC binary format to a verifying key.""" + # based on code from https://github.com/richardkiss/pycoin + if string.startswith(b('\x04')): + # uncompressed + return klass.from_string(string[1:], curve, hashfunc, + validate_point) + elif string.startswith(b('\x02')) or string.startswith(b('\x03')): + # compressed + is_even = string.startswith(b('\x02')) + x = string_to_number(string[1:]) + order = curve.order + p = curve.curve.p() + alpha = (pow(x, 3, p) + (curve.curve.a() * x) + curve.curve.b()) % p + beta = square_root_mod_prime(alpha, p) + if is_even == bool(beta & 1): + y = p - beta + else: + y = beta + if validate_point: + assert ecdsa.point_is_valid(curve.generator, x, y) + point = ellipticcurve.Point(curve.curve, x, y, order) + return klass.from_public_point(point, curve, hashfunc) + def to_string(self): # VerifyingKey.from_string(vk.to_string()) == vk as long as the # curves are the same: the curve itself is not included in the @@ -99,6 +127,20 @@ def to_der(self): self.curve.encoded_oid), der.encode_bitstring(point_str)) + def to_sec(self, compressed=True): + """Convert verifying key to the SEC binary format (as used by OpenSSL).""" + # based on code from https://github.com/richardkiss/pycoin + order = self.pubkey.order + x_str = number_to_string(self.pubkey.point.x(), order) + if compressed: + if self.pubkey.point.y() & 1: + return b('\x03') + x_str + else: + return b('\x02') + x_str + else: + y_str = number_to_string(self.pubkey.point.y(), order) + return b('\x04') + x_str + y_str + def verify(self, signature, data, hashfunc=None, sigdecode=sigdecode_string): hashfunc = hashfunc or self.default_hashfunc digest = hashfunc(data).digest() diff --git a/ecdsa/test_pyecdsa.py b/ecdsa/test_pyecdsa.py index 7648c2fa..acbaf5ac 100644 --- a/ecdsa/test_pyecdsa.py +++ b/ecdsa/test_pyecdsa.py @@ -54,6 +54,21 @@ def test_basic(self): pub2 = VerifyingKey.from_string(pub.to_string()) self.assertTrue(pub2.verify(sig, data)) + def test_sec(self): + for i in range (20): + skey = SigningKey.generate() + pkey0 = skey.get_verifying_key() + sec0 = pkey0.to_sec() + pkey1 = VerifyingKey.from_sec (sec0) + self.assertEqual(pkey0.to_string(), pkey1.to_string()) + pkey2 = VerifyingKey.from_sec(unhexlify(b( + '045b9dfc2af65bd5fb0bd01103ab21e9cfeb1eeafa10795d6801b14e09beadd7f8' + '55981f2803fc3c07edfc2435fbf2326d65d3f237f0bcc2399514255b8d4285c5')), + curve=SECP256k1) + self.assertEqual( + pkey2.to_sec(compressed=True), unhexlify(b( + '035b9dfc2af65bd5fb0bd01103ab21e9cfeb1eeafa10795d6801b14e09beadd7f8'))) + def test_deterministic(self): data = b("blahblah") secexp = int("9d0219792467d7d37b4d43298a7d0c05", 16) @@ -253,6 +268,16 @@ def test_pubkey_strings(self): VerifyingKey.from_der, pub1_der + b("junk")) badpub = VerifyingKey.from_der(pub1_der) + pub1_sec = pub1.to_sec(compressed=False) + self.assertEqual(type(pub1_sec), binary_type) + pub2 = VerifyingKey.from_sec(pub1_sec, curve=NIST256p) + self.assertTruePubkeysEqual(pub1, pub2) + + pub1_sec = pub1.to_sec(compressed=True) + self.assertEqual(type(pub1_sec), binary_type) + pub2 = VerifyingKey.from_sec(pub1_sec, curve=NIST256p) + self.assertTruePubkeysEqual(pub1, pub2) + class FakeGenerator: def order(self): return 123456789