Skip to content

Commit

Permalink
Pk Recovery (#102)
Browse files Browse the repository at this point in the history
* Added recover_public_keys() to Signature
* Added pk recovery support in wrapper
  • Loading branch information
gianlucafrei authored and tomato42 committed Jan 15, 2019
1 parent a7f1db6 commit 795b861
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
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

0 comments on commit 795b861

Please sign in to comment.