Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/ecdsa/ecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,34 @@ def __init__(self, r, s):
self.r = r
self.s = s

def recover_public_keys(self, hash, generator):
"""Returns two public keys for which the signature is valid
hash is signed hash
generator is the used generator of the signature
"""
curve = generator.curve()
n = generator.order()
r = self.r
s = self.s
e = hash
x = r

# Compute the curve point with x as x-coordinate
alpha = (pow(x, 3, curve.p()) + (curve.a() * x) + curve.b()) % curve.p()
beta = numbertheory.square_root_mod_prime(alpha, curve.p())
y = beta if beta % 2 == 0 else curve.p() - beta

# Compute the public key
R1 = ellipticcurve.Point(curve, x, y, n)
Q1 = numbertheory.inverse_mod(r, n) * (s * R1 + (-e % n) * generator)
Pk1 = Public_key(generator, Q1)

# And the second solution
R2 = ellipticcurve.Point(curve, x, -y, n)
Q2 = numbertheory.inverse_mod(r, n) * (s * R2 + (-e % n) * generator)
Pk2 = Public_key(generator, Q2)

return [Pk1, Pk2]

class Public_key(object):
"""Public key for ECDSA.
Expand Down
24 changes: 24 additions & 0 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ def from_der(klass, string):
assert point_str.startswith(b("\x00\x04"))
return klass.from_string(point_str[2:], curve)

@classmethod
def from_public_key_recovery(klass, signature, data, curve, hashfunc=sha1, sigdecode=sigdecode_string):
# Given a signature and corresponding message this function
# returns a list of verifying keys for this signature and message

digest = hashfunc(data).digest()
return klass.from_public_key_recovery_with_digest(signature, digest, curve, hashfunc=sha1, sigdecode=sigdecode_string)

@classmethod
def from_public_key_recovery_with_digest(klass, signature, digest, curve, hashfunc=sha1, sigdecode=sigdecode_string):
# Given a signature and corresponding digest this function
# returns a list of verifying keys for this signature and message

generator = curve.generator
r, s = sigdecode(signature, generator.order())
sig = ecdsa.Signature(r, s)

digest_as_number = string_to_number(digest)
pks = sig.recover_public_keys(digest_as_number, generator)

# Transforms the ecdsa.Public_key object into a VerifyingKey
verifying_keys = [klass.from_public_point(pk.point, curve, hashfunc) for pk in pks]
return verifying_keys

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
Expand Down
29 changes: 29 additions & 0 deletions src/ecdsa/test_ecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,33 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected):
raise TestFailure("*** Signature test failed: got %s, expected %s." % \
(got, expected))

def test_pk_recovery(Msg, R, S, Qx, Qy):

sign = Signature(R,S)
pks = sign.recover_public_keys(digest_integer(Msg), generator_192)

print_("Test pk recover")

if pks:

# Test if the signature is valid for all found public keys
for pk in pks:
q = pk.point
print_("Recovered q: %s" % q)
test_signature_validity(Msg, q.x(), q.y(), R, S, True)

# Test if the original public key is in the set of found keys
original_q = ellipticcurve.Point(curve_192, Qx, Qy)
points = [pk.point for pk in pks]
if original_q in points:
print_("Original q was found")
else:
raise TestFailure("Original q is not in the list of recovered public keys")

else:
raise TestFailure("*** NO valid public key returned")


print_("NIST Curve P-192:")

p192 = generator_192
Expand Down Expand Up @@ -165,13 +192,15 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected):
R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916
S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479
test_signature_validity(Msg, Qx, Qy, R, S, True)
test_pk_recovery(Msg, R, S, Qx, Qy)

Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4
Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7
Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7
R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af
S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c
test_signature_validity(Msg, Qx, Qy, R, S, True)
test_pk_recovery(Msg, R, S, Qx, Qy)

Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd
Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7
Expand Down
27 changes: 27 additions & 0 deletions src/ecdsa/test_pyecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,33 @@ def test_hashfunc(self):
curve=NIST256p)
self.assertTrue(vk3.verify(sig, data, hashfunc=sha256))

def test_public_key_recovery(self):
# Create keys
curve = NIST256p

sk = SigningKey.generate(curve=curve)
vk = sk.get_verifying_key()

# Sign a message
data = b("blahblah")
signature = sk.sign(data)

# Recover verifying keys
recovered_vks = VerifyingKey.from_public_key_recovery(signature, data, curve)

# Test if each pk is valid
for recovered_vk in recovered_vks:
# Test if recovered vk is valid for the data
self.assertTrue(recovered_vk.verify(signature, data))

# Test if properties are equal
self.assertEqual(vk.curve, recovered_vk.curve)
self.assertEqual(vk.default_hashfunc, recovered_vk.default_hashfunc)

# Test if original vk is the list of recovered keys
self.assertTrue(
vk.pubkey.point in [recovered_vk.pubkey.point for recovered_vk in recovered_vks])


class OpenSSL(unittest.TestCase):
# test interoperability with OpenSSL tools. Note that openssl's ECDSA
Expand Down