summaryrefslogtreecommitdiffstats
path: root/python/PyECC/ecc/ecdsa.py
blob: 6b52aeaa5de64d4626dc83b32c71df558174cf2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#
#   Elliptic Curve Digital Signature Algorithm (ECDSA)
#
#   COPYRIGHT (c) 2010 by Toni Mattis <solaris@live.de>
#

from elliptic import inv, mulf, mulp, muladdp, element
from curves import get_curve, implemented_keys
from os import urandom

import hashlib

def randkey(bits, n):
    '''Generate a random number (mod n) having the specified bit length'''
    rb = urandom(bits / 8 + 8)  # + 64 bits as recommended in FIPS 186-3
    c = 0
    for r in rb:
        c = (c << 8) | ord(r)
    return (c % (n - 1)) + 1

def keypair(bits):
    '''Generate a new keypair (qk, dk) with dk = private and qk = public key'''
    try:
        bits, cn, n, cp, cq, g = get_curve(bits)
    except KeyError:
        raise ValueError, "Key size %s not implemented" % bits
    if n > 0:
        d = randkey(bits, n)
        q = mulp(cp, cq, cn, g, d)
        return (bits, q), (bits, d)
    else:
        raise ValueError, "Key size %s not suitable for signing" % bits

def supported_keys():
    '''Return a list of all key sizes implemented for signing'''
    return implemented_keys(True)

def validate_public_key(qk):
    '''Check whether public key qk is valid'''
    bits, q = qk
    x, y = q
    bits, cn, n, cp, cq, g = get_curve(bits)
    return q and 0 < x < cn and 0 < y < cn and \
        element(q, cp, cq, cn) and (mulp(cp, cq, cn, q, n) == None)

def validate_private_key(dk):
    '''Check whether private key dk is valid'''
    bits, d = dk
    bits, cn, n, cp, cq, g = get_curve(bits)
    return 0 < d < cn

def match_keys(qk, dk):
    '''Check whether dk is the private key belonging to qk'''
    bits, d = dk
    bitz, q = qk
    if bits == bitz:
        bits, cn, n, cp, cq, g = get_curve(bits)
        return mulp(cp, cq, cn, g, d) == q
    else:
        return False

def truncate(h, hmax):
    '''Truncate a hash to the bit size of hmax'''
    while h > hmax:
        h >>= 1
    return h

def sign(h, dk):
    '''Sign the numeric value h using private key dk'''
    bits, d = dk
    bits, cn, n, cp, cq, g = get_curve(bits)
    h = truncate(h, cn)
    r = s = 0
    while r == 0 or s == 0:
        k = randkey(bits, cn)
        kinv = inv(k, n)
        kg = mulp(cp, cq, cn, g, k)
        r = kg[0] % n
        if r == 0:
            continue
        s = (kinv * (h + r * d)) % n
    return r, s

def verify(h, sig, qk):
    '''Verify that 'sig' is a valid signature of h using public key qk'''
    bits, q = qk
    try:
        bits, cn, n, cp, cq, g = get_curve(bits)
    except KeyError:
        return False
    h = truncate(h, cn)
    r, s = sig
    if 0 < r < n and 0 < s < n:
        w = inv(s, n)
        u1 = (h * w) % n
        u2 = (r * w) % n
        x, y = muladdp(cp, cq, cn, g, u1, q, u2)
        return r % n == x % n
    return False

def hash_sign(s, dk, hashfunc = 'sha256'):
    h = int(hashlib.new(hashfunc, s).hexdigest(), 16)
    return (hashfunc,) + sign(h, dk)

def hash_verify(s, sig, qk):
    h = int(hashlib.new(sig[0], s).hexdigest(), 16)
    return verify(h, sig[1:], qk)


if __name__ == "__main__":

    import time

    testh1 = 0x0123456789ABCDEF
    testh2 = 0x0123456789ABCDEE

    for k in supported_keys():
        qk, dk = keypair(k)
        s1 = sign(testh1, dk)
        s2 = sign(testh1, (dk[0], dk[1] ^ 1))
        s3 = (s1[0], s1[1] ^ 1)
        qk2 = (qk[0], (qk[1][0] ^ 1, qk[1][1]))
        
        assert verify(testh1, s1, qk)       # everything ok -> must succeed
        assert not verify(testh2, s1, qk)   # modified hash       -> must fail
        assert not verify(testh1, s2, qk)   # different priv. key -> must fail
        assert not verify(testh1, s3, qk)   # modified signature  -> must fail
        assert not verify(testh1, s1, qk2)  # different publ. key -> must fail


    def test_perf(bits, rounds = 50):
        '''-> (key generations, signatures, verifications) / second'''
        h = 0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
        d = get_curve(bits)
        
        t = time.time()
        for i in xrange(rounds):
            qk, dk = keypair(bits)
        tgen = time.time() - t
        
        t = time.time()
        for i in xrange(rounds):
            s = sign(0, dk)
        tsign = time.time() - t

        t = time.time()
        for i in xrange(rounds):
            verify(0, s, qk)
        tver = time.time() - t

        return rounds / tgen, rounds / tsign, rounds / tver