Merge branch 'tap-out-phase-1' into 'main'
[tor.git] / src / test / ed25519_exts_ref.py
blobabc9a1de7fb120aafe7a76d1f408f689df15b030
1 #!/usr/bin/python
2 # Copyright 2014-2019, The Tor Project, Inc
3 # See LICENSE for licensing information
5 """
6 Reference implementations for the ed25519 tweaks that Tor uses.
8 Includes self-tester and test vector generator.
9 """
11 # Future imports for Python 2.7, mandatory in 3.0
12 from __future__ import division
13 from __future__ import print_function
14 from __future__ import unicode_literals
16 import slow_ed25519
17 from slow_ed25519 import *
19 import os
20 import random
21 import slownacl_curve25519
22 import unittest
23 import binascii
24 import textwrap
26 #define a synonym that doesn't look like 1
27 ell = l
29 # This replaces expmod above and makes it go a lot faster.
30 slow_ed25519.expmod = pow
32 def curve25519ToEd25519(c, sign):
33 u = decodeint(c)
34 y = ((u - 1) * inv(u + 1)) % q
35 x = xrecover(y)
36 if x & 1 != sign: x = q-x
37 return encodepoint([x,y])
39 def blindESK(esk, param):
40 mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2))
41 s = decodeint(esk[:32])
42 s_prime = (s * mult) % ell
43 k = esk[32:]
44 assert(len(k) == 32)
45 k_prime = H(b"Derive temporary signing key hash input" + k)[:32]
46 return encodeint(s_prime) + k_prime
48 def blindPK(pk, param):
49 mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2))
50 P = decodepoint(pk)
51 return encodepoint(scalarmult(P, mult))
53 def expandSK(sk):
54 h = H(sk)
55 a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
56 k = bytes(h[i] for i in range(b//8,b//4))
57 assert len(k) == 32
58 return encodeint(a)+k
60 def publickeyFromESK(h):
61 a = decodeint(h[:32])
62 A = scalarmult(B,a)
63 return encodepoint(A)
65 def signatureWithESK(m,h,pk):
66 a = decodeint(h[:32])
67 r = Hint(bytes([h[i] for i in range(b//8,b//4)]) + m)
68 R = scalarmult(B,r)
69 S = (r + Hint(encodepoint(R) + pk + m) * a) % l
70 return encodepoint(R) + encodeint(S)
72 def newSK():
73 return os.urandom(32)
75 def random_scalar(entropy_f): # 0..L-1 inclusive
76 # reduce the bias to a safe level by generating 256 extra bits
77 oversized = int(binascii.hexlify(entropy_f(32+32)), 16)
78 return oversized % ell
80 # ------------------------------------------------------------
82 MSG = "This is extremely silly. But it is also incredibly serious business!"
84 class SelfTest(unittest.TestCase):
86 def _testSignatures(self, esk, pk):
87 sig = signatureWithESK(MSG, esk, pk)
88 checkvalid(sig, MSG, pk)
89 bad = False
90 try:
91 checkvalid(sig, MSG*2, pk)
92 bad = True
93 except Exception:
94 pass
96 self.failIf(bad)
98 def testExpand(self):
99 sk = newSK()
100 pk = publickey(sk)
101 esk = expandSK(sk)
102 sig1 = signature(MSG, sk, pk)
103 sig2 = signatureWithESK(MSG, esk, pk)
104 self.assertEquals(sig1, sig2)
106 def testSignatures(self):
107 sk = newSK()
108 esk = expandSK(sk)
109 pk = publickeyFromESK(esk)
110 pk2 = publickey(sk)
111 self.assertEquals(pk, pk2)
113 self._testSignatures(esk, pk)
115 def testDerivation(self):
116 priv = slownacl_curve25519.Private()
117 pub = priv.get_public()
119 ed_pub0 = publickeyFromESK(priv.private)
120 sign = (ord(ed_pub0[31]) & 255) >> 7
121 ed_pub1 = curve25519ToEd25519(pub.public, sign)
123 self.assertEquals(ed_pub0, ed_pub1)
125 def testBlinding(self):
126 sk = newSK()
127 esk = expandSK(sk)
128 pk = publickeyFromESK(esk)
129 param = os.urandom(32)
130 besk = blindESK(esk, param)
131 bpk = blindPK(pk, param)
132 bpk2 = publickeyFromESK(besk)
133 self.assertEquals(bpk, bpk2)
135 self._testSignatures(besk, bpk)
137 def testIdentity(self):
138 # Base point:
139 # B is the unique point (x, 4/5) \in E for which x is positive
140 By = 4 * inv(5)
141 Bx = xrecover(By)
142 B = [Bx % q,By % q]
144 # Get identity E by doing: E = l*B, where l is the group order
145 identity = scalarmult(B, ell)
147 # Get identity E by doing: E = l*A, where A is a random point
148 sk = newSK()
149 pk = decodepoint(publickey(sk))
150 identity2 = scalarmult(pk, ell)
152 # Check that identities match
153 assert(identity == identity2)
154 # Check that identity is the point (0,1)
155 assert(identity == [0,1])
157 # Check identity element: a*E = E, where a is a random scalar
158 scalar = random_scalar(os.urandom)
159 result = scalarmult(identity, scalar)
160 assert(result == identity == identity2)
162 # ------------------------------------------------------------
164 # From pprint.pprint([ binascii.b2a_hex(os.urandom(32)) for _ in xrange(8) ])
165 RAND_INPUTS = [
166 '26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d36',
167 'fba7a5366b5cb98c2667a18783f5cf8f4f8d1a2ce939ad22a6e685edde85128d',
168 '67e3aa7a14fac8445d15e45e38a523481a69ae35513c9e4143eb1c2196729a0e',
169 'd51385942033a76dc17f089a59e6a5a7fe80d9c526ae8ddd8c3a506b99d3d0a6',
170 '5c8eac469bb3f1b85bc7cd893f52dc42a9ab66f1b02b5ce6a68e9b175d3bb433',
171 'eda433d483059b6d1ff8b7cfbd0fe406bfb23722c8f3c8252629284573b61b86',
172 '4377c40431c30883c5fbd9bc92ae48d1ed8a47b81d13806beac5351739b5533d',
173 'c6bbcce615839756aed2cc78b1de13884dd3618f48367a17597a16c1cd7a290b',
174 'c6bbcce615839756aed2cc78b1de13884dd3618f48367a17597a16c1cd7a290b',
175 'c6bbcce615839756aed2cc78b1de13884dd3618f48367a17597a16c1cd7a290b']
177 # From pprint.pprint([ binascii.b2a_hex(os.urandom(32)) for _ in xrange(8) ])
178 BLINDING_PARAMS = [
179 '54a513898b471d1d448a2f3c55c1de2c0ef718c447b04497eeb999ed32027823',
180 '831e9b5325b5d31b7ae6197e9c7a7baf2ec361e08248bce055908971047a2347',
181 'ac78a1d46faf3bfbbdc5af5f053dc6dc9023ed78236bec1760dadfd0b2603760',
182 'f9c84dc0ac31571507993df94da1b3d28684a12ad14e67d0a068aba5c53019fc',
183 'b1fe79d1dec9bc108df69f6612c72812755751f21ecc5af99663b30be8b9081f',
184 '81f1512b63ab5fb5c1711a4ec83d379c420574aedffa8c3368e1c3989a3a0084',
185 '97f45142597c473a4b0e9a12d64561133ad9e1155fe5a9807fe6af8a93557818',
186 '3f44f6a5a92cde816635dfc12ade70539871078d2ff097278be2a555c9859cd0',
187 '0000000000000000000000000000000000000000000000000000000000000000',
188 '1111111111111111111111111111111111111111111111111111111111111111']
190 PREFIX = "ED25519_"
192 def writeArray(name, array):
193 print("static const char *{prefix}{name}[] = {{".format(
194 prefix=PREFIX,name=name))
195 for a in array:
196 h = binascii.b2a_hex(a)
197 if len(h) > 70:
198 h1 = h[:70]
199 h2 = h[70:]
200 print(' "{0}"\n "{1}",'.format(h1.decode('utf-8'),h2.decode('utf-8')))
201 else:
202 print(' "{0}",'.format(h.decode('utf-8')))
203 print("};\n")
205 def comment(text, initial="/**"):
206 print(initial)
207 print(textwrap.fill(text,initial_indent=" * ",subsequent_indent=" * "))
208 print(" */")
210 def makeTestVectors():
211 comment("""Test vectors for our ed25519 implementation and related
212 functions. These were automatically generated by the
213 ed25519_exts_ref.py script.""", initial="/*")
216 comment("""Secret key seeds used as inputs for the ed25519 test vectors.
217 Randomly generated. """)
218 secretKeys = [ binascii.a2b_hex(r) for r in RAND_INPUTS ]
219 writeArray("SECRET_KEYS", secretKeys)
221 comment("""Secret ed25519 keys after expansion from seeds. This is how Tor
222 represents them internally.""")
223 expandedSecretKeys = [ expandSK(sk) for sk in secretKeys ]
224 writeArray("EXPANDED_SECRET_KEYS", expandedSecretKeys)
226 comment("""Public keys derived from the above secret keys""")
227 publicKeys = [ publickey(sk) for sk in secretKeys ]
228 writeArray("PUBLIC_KEYS", publicKeys)
230 comment("""The curve25519 public keys from which the ed25519 keys can be
231 derived. Used to test our 'derive ed25519 from curve25519'
232 code.""")
233 writeArray("CURVE25519_PUBLIC_KEYS",
234 (slownacl_curve25519.smult_curve25519_base(sk[:32])
235 for sk in expandedSecretKeys))
237 comment("""Parameters used for key blinding tests. Randomly generated.""")
238 blindingParams = [ binascii.a2b_hex(r) for r in BLINDING_PARAMS ]
239 writeArray("BLINDING_PARAMS", blindingParams)
241 comment("""Blinded secret keys for testing key blinding. The nth blinded
242 key corresponds to the nth secret key blidned with the nth
243 blinding parameter.""")
244 writeArray("BLINDED_SECRET_KEYS",
245 (blindESK(expandSK(sk), bp)
246 for sk,bp in zip(secretKeys,blindingParams)))
248 comment("""Blinded public keys for testing key blinding. The nth blinded
249 key corresponds to the nth public key blidned with the nth
250 blinding parameter.""")
251 writeArray("BLINDED_PUBLIC_KEYS",
252 (blindPK(pk, bp) for pk,bp in zip(publicKeys,blindingParams)))
254 comment("""Signatures of the public keys, made with their corresponding
255 secret keys.""")
256 writeArray("SELF_SIGNATURES",
257 (signature(pk, sk, pk) for pk,sk in zip(publicKeys,secretKeys)))
261 if __name__ == '__main__':
262 import sys
263 if len(sys.argv) == 1 or sys.argv[1] not in ("SelfTest", "MakeVectors"):
264 print("You should specify one of 'SelfTest' or 'MakeVectors'")
265 sys.exit(1)
266 if sys.argv[1] == 'SelfTest':
267 unittest.main()
268 else:
269 makeTestVectors()