├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.rst
├── ed25519.py
├── science.py
├── setup.cfg
├── setup.py
├── test_data
└── ed25519
├── test_ed25519.py
└── tox.ini
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request: {}
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | ci:
10 | runs-on: ubuntu-20.04
11 | strategy:
12 | matrix:
13 | PYTHON:
14 | - {VERSION: "3.5", TOXENV: "py35"}
15 | - {VERSION: "3.6", TOXENV: "py36"}
16 | - {VERSION: "3.7", TOXENV: "py37"}
17 | - {VERSION: "3.8", TOXENV: "py38"}
18 | - {VERSION: "3.9", TOXENV: "py39"}
19 | - {VERSION: "3.10", TOXENV: "py310"}
20 | - {VERSION: "3.11", TOXENV: "py311"}
21 | - {VERSION: "3.12", TOXENV: "py312"}
22 | - {VERSION: "pypy-3.7", TOXENV: "pypy3"}
23 | - {VERSION: "pypy-3.8", TOXENV: "pypy3"}
24 | - {VERSION: "pypy-3.9", TOXENV: "pypy3"}
25 | - {VERSION: "pypy-3.10", TOXENV: "pypy3"}
26 | - {VERSION: "3.6", TOXENV: "pep8"}
27 | steps:
28 | - uses: actions/checkout@v4
29 | - name: Setup python
30 | uses: actions/setup-python@v5
31 | with:
32 | python-version: ${{ matrix.PYTHON.VERSION }}
33 | - run: pip install tox
34 | - run: tox
35 | env:
36 | TOXENV: ${{ matrix.PYTHON.TOXENV }}
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .tox
3 | *.egg-info
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ed25519
2 | =======
3 |
4 | .. image:: https://github.com/pyca/ed25519/workflows/CI/badge.svg
5 | :target: https://github.com/pyca/ed25519/actions?query=workflow%3ACI
6 |
7 | `Ed25519 `_ is a high-speed public-key
8 | signature system. ``ed25519.py`` is based on the original Python
9 | implementation published on the Ed25519 website, with major
10 | optimizations to make it run reasonably fast.
11 |
12 |
13 | Warning
14 | -------
15 |
16 | This code is **not safe** for use with secret data. Even operating on
17 | public data (i.e., verifying public signatures on public messages), it
18 | is slower than alternatives.
19 |
20 | The issue is that our computations may behave differently depending on
21 | their inputs in ways that could reveal those inputs to an attacker;
22 | they may take different amounts of time, and may have different memory
23 | access patterns. These side-channel attacks are difficult to avoid in
24 | Python, except perhaps with major sacrifice of efficiency.
25 |
26 | This code may be useful in cases where you absolutely cannot have any
27 | C code dependencies. Otherwise, `PyNaCl
28 | `_ provides a version of the original
29 | author's C implementation, which runs faster and is carefully
30 | engineered to avoid side-channel attacks.
31 |
32 |
33 | Running the tests
34 | -----------------
35 |
36 | ``ed25519.py`` uses tox to run the test suite. You can run all the tests by using:
37 |
38 | .. code:: bash
39 |
40 | $ tox
41 |
42 |
43 | Resources
44 | ---------
45 |
46 | * `IRC `_
47 | (#cryptography-dev - irc.freenode.net)
48 |
--------------------------------------------------------------------------------
/ed25519.py:
--------------------------------------------------------------------------------
1 | # ed25519.py - Optimized version of the reference implementation of Ed25519
2 | #
3 | # Written in 2011? by Daniel J. Bernstein
4 | # 2013 by Donald Stufft
5 | # 2013 by Alex Gaynor
6 | # 2013 by Greg Price
7 | #
8 | # To the extent possible under law, the author(s) have dedicated all copyright
9 | # and related and neighboring rights to this software to the public domain
10 | # worldwide. This software is distributed without any warranty.
11 | #
12 | # You should have received a copy of the CC0 Public Domain Dedication along
13 | # with this software. If not, see
14 | # .
15 |
16 | """
17 | NB: This code is not safe for use with secret keys or secret data.
18 | The only safe use of this code is for verifying signatures on public messages.
19 |
20 | Functions for computing the public key of a secret key and for signing
21 | a message are included, namely publickey_unsafe and signature_unsafe,
22 | for testing purposes only.
23 |
24 | The root of the problem is that Python's long-integer arithmetic is
25 | not designed for use in cryptography. Specifically, it may take more
26 | or less time to execute an operation depending on the values of the
27 | inputs, and its memory access patterns may also depend on the inputs.
28 | This opens it to timing and cache side-channel attacks which can
29 | disclose data to an attacker. We rely on Python's long-integer
30 | arithmetic, so we cannot handle secrets without risking their disclosure.
31 | """
32 |
33 | import hashlib
34 |
35 |
36 | __version__ = "1.0.dev0"
37 |
38 |
39 | b = 256
40 | q = 2**255 - 19
41 | l = 2**252 + 27742317777372353535851937790883648493
42 |
43 |
44 | def H(m):
45 | return hashlib.sha512(m).digest()
46 |
47 |
48 | def pow2(x, p):
49 | """== pow(x, 2**p, q)"""
50 | while p > 0:
51 | x = x * x % q
52 | p -= 1
53 | return x
54 |
55 |
56 | def inv(z):
57 | r"""$= z^{-1} \mod q$, for z != 0"""
58 | # Adapted from curve25519_athlon.c in djb's Curve25519.
59 | z2 = z * z % q # 2
60 | z9 = pow2(z2, 2) * z % q # 9
61 | z11 = z9 * z2 % q # 11
62 | z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0
63 | z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
64 | z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
65 | z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
66 | z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
67 | z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
68 | z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
69 | z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
70 | return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
71 |
72 |
73 | d = -121665 * inv(121666) % q
74 | I = pow(2, (q - 1) // 4, q)
75 |
76 |
77 | def xrecover(y):
78 | xx = (y * y - 1) * inv(d * y * y + 1)
79 | x = pow(xx, (q + 3) // 8, q)
80 |
81 | if (x * x - xx) % q != 0:
82 | x = (x * I) % q
83 |
84 | if x % 2 != 0:
85 | x = q - x
86 |
87 | return x
88 |
89 |
90 | By = 4 * inv(5)
91 | Bx = xrecover(By)
92 | B = (Bx % q, By % q, 1, (Bx * By) % q)
93 | ident = (0, 1, 1, 0)
94 |
95 |
96 | def edwards_add(P, Q):
97 | # This is formula sequence 'addition-add-2008-hwcd-3' from
98 | # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
99 | (x1, y1, z1, t1) = P
100 | (x2, y2, z2, t2) = Q
101 |
102 | a = (y1 - x1) * (y2 - x2) % q
103 | b = (y1 + x1) * (y2 + x2) % q
104 | c = t1 * 2 * d * t2 % q
105 | dd = z1 * 2 * z2 % q
106 | e = b - a
107 | f = dd - c
108 | g = dd + c
109 | h = b + a
110 | x3 = e * f
111 | y3 = g * h
112 | t3 = e * h
113 | z3 = f * g
114 |
115 | return (x3 % q, y3 % q, z3 % q, t3 % q)
116 |
117 |
118 | def edwards_double(P):
119 | # This is formula sequence 'dbl-2008-hwcd' from
120 | # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
121 | (x1, y1, z1, t1) = P
122 |
123 | a = x1 * x1 % q
124 | b = y1 * y1 % q
125 | c = 2 * z1 * z1 % q
126 | # dd = -a
127 | e = ((x1 + y1) * (x1 + y1) - a - b) % q
128 | g = -a + b # dd + b
129 | f = g - c
130 | h = -a - b # dd - b
131 | x3 = e * f
132 | y3 = g * h
133 | t3 = e * h
134 | z3 = f * g
135 |
136 | return (x3 % q, y3 % q, z3 % q, t3 % q)
137 |
138 |
139 | def scalarmult(P, e):
140 | if e == 0:
141 | return ident
142 | Q = scalarmult(P, e // 2)
143 | Q = edwards_double(Q)
144 | if e & 1:
145 | Q = edwards_add(Q, P)
146 | return Q
147 |
148 |
149 | # Bpow[i] == scalarmult(B, 2**i)
150 | Bpow = []
151 |
152 |
153 | def make_Bpow():
154 | P = B
155 | for i in range(253):
156 | Bpow.append(P)
157 | P = edwards_double(P)
158 |
159 |
160 | make_Bpow()
161 |
162 |
163 | def scalarmult_B(e):
164 | """
165 | Implements scalarmult(B, e) more efficiently.
166 | """
167 | # scalarmult(B, l) is the identity
168 | e = e % l
169 | P = ident
170 | for i in range(253):
171 | if e & 1:
172 | P = edwards_add(P, Bpow[i])
173 | e = e // 2
174 | assert e == 0, e
175 | return P
176 |
177 |
178 | def encodeint(y):
179 | bits = [(y >> i) & 1 for i in range(b)]
180 | return bytes(
181 | [sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(b // 8)]
182 | )
183 |
184 |
185 | def encodepoint(P):
186 | (x, y, z, t) = P
187 | zi = inv(z)
188 | x = (x * zi) % q
189 | y = (y * zi) % q
190 | bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
191 | return bytes(
192 | [sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(b // 8)]
193 | )
194 |
195 |
196 | def bit(h, i):
197 | return (h[i // 8] >> (i % 8)) & 1
198 |
199 |
200 | def publickey_unsafe(sk):
201 | """
202 | Not safe to use with secret keys or secret data.
203 |
204 | See module docstring. This function should be used for testing only.
205 | """
206 | h = H(sk)
207 | a = 2 ** (b - 2) + sum(2**i * bit(h, i) for i in range(3, b - 2))
208 | A = scalarmult_B(a)
209 | return encodepoint(A)
210 |
211 |
212 | def Hint(m):
213 | h = H(m)
214 | return sum(2**i * bit(h, i) for i in range(2 * b))
215 |
216 |
217 | def signature_unsafe(m, sk, pk):
218 | """
219 | Not safe to use with secret keys or secret data.
220 |
221 | See module docstring. This function should be used for testing only.
222 | """
223 | h = H(sk)
224 | a = 2 ** (b - 2) + sum(2**i * bit(h, i) for i in range(3, b - 2))
225 | r = Hint(bytes([h[j] for j in range(b // 8, b // 4)]) + m)
226 | R = scalarmult_B(r)
227 | S = (r + Hint(encodepoint(R) + pk + m) * a) % l
228 | return encodepoint(R) + encodeint(S)
229 |
230 |
231 | def isoncurve(P):
232 | (x, y, z, t) = P
233 | return (
234 | z % q != 0
235 | and x * y % q == z * t % q
236 | and (y * y - x * x - z * z - d * t * t) % q == 0
237 | )
238 |
239 |
240 | def decodeint(s):
241 | return sum(2**i * bit(s, i) for i in range(0, b))
242 |
243 |
244 | def decodepoint(s):
245 | y = sum(2**i * bit(s, i) for i in range(0, b - 1))
246 | x = xrecover(y)
247 | if x & 1 != bit(s, b - 1):
248 | x = q - x
249 | P = (x, y, 1, (x * y) % q)
250 | if not isoncurve(P):
251 | raise ValueError("decoding point that is not on curve")
252 | return P
253 |
254 |
255 | class SignatureMismatch(Exception):
256 | pass
257 |
258 |
259 | def checkvalid(s, m, pk):
260 | """
261 | Not safe to use when any argument is secret.
262 |
263 | See module docstring. This function should be used only for
264 | verifying public signatures of public messages.
265 | """
266 | if len(s) != b // 4:
267 | raise ValueError("signature length is wrong")
268 |
269 | if len(pk) != b // 8:
270 | raise ValueError("public-key length is wrong")
271 |
272 | R = decodepoint(s[: b // 8])
273 | A = decodepoint(pk)
274 | S = decodeint(s[b // 8 : b // 4])
275 | h = Hint(encodepoint(R) + pk + m)
276 |
277 | (x1, y1, z1, t1) = P = scalarmult_B(S)
278 | (x2, y2, z2, t2) = Q = edwards_add(R, scalarmult(A, h))
279 |
280 | if (
281 | not isoncurve(P)
282 | or not isoncurve(Q)
283 | or (x1 * z2 - x2 * z1) % q != 0
284 | or (y1 * z2 - y2 * z1) % q != 0
285 | ):
286 | raise SignatureMismatch("signature does not pass verification")
287 |
--------------------------------------------------------------------------------
/science.py:
--------------------------------------------------------------------------------
1 | # ed25519.py - Optimized version of the reference implementation of Ed25519
2 | #
3 | # Written in 2011? by Daniel J. Bernstein
4 | # 2013 by Donald Stufft
5 | # 2013 by Alex Gaynor
6 | # 2013 by Greg Price
7 | #
8 | # To the extent possible under law, the author(s) have dedicated all copyright
9 | # and related and neighboring rights to this software to the public domain
10 | # worldwide. This software is distributed without any warranty.
11 | #
12 | # You should have received a copy of the CC0 Public Domain Dedication along
13 | # with this software. If not, see
14 | # .
15 | import os
16 | import timeit
17 |
18 | import ed25519
19 |
20 |
21 | seed = os.urandom(32)
22 |
23 | data = b"The quick brown fox jumps over the lazy dog"
24 | private_key = seed
25 | public_key = ed25519.publickey_unsafe(seed)
26 | signature = ed25519.signature_unsafe(data, private_key, public_key)
27 |
28 | print("\nTime verify signature")
29 | print(
30 | timeit.timeit(
31 | "ed25519.checkvalid(signature, data, public_key)",
32 | setup="from __main__ import ed25519, signature, data, public_key",
33 | number=100,
34 | )
35 | )
36 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = 1
3 |
4 | [check-manifest]
5 | ignore =
6 | .travis.yml
7 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | import ed25519
4 |
5 |
6 | setup(
7 | name="ed25519.py",
8 | version=ed25519.__version__,
9 | py_modules=["ed25519"],
10 | zip_safe=False,
11 | )
12 |
--------------------------------------------------------------------------------
/test_ed25519.py:
--------------------------------------------------------------------------------
1 | # ed25519.py - Optimized version of the reference implementation of Ed25519
2 | #
3 | # Written in 2011? by Daniel J. Bernstein
4 | # 2013 by Donald Stufft
5 | # 2013 by Alex Gaynor
6 | # 2013 by Greg Price
7 | #
8 | # To the extent possible under law, the author(s) have dedicated all copyright
9 | # and related and neighboring rights to this software to the public domain
10 | # worldwide. This software is distributed without any warranty.
11 | #
12 | # You should have received a copy of the CC0 Public Domain Dedication along
13 | # with this software. If not, see
14 | # .
15 | import binascii
16 | import codecs
17 | import os
18 |
19 | import pytest
20 |
21 | import ed25519
22 |
23 |
24 | def ed25519_known_answers():
25 | # Known answers taken from: http://ed25519.cr.yp.to/python/sign.input
26 | # File Format is a lined based file where each line has sk, pk, m, sm
27 | # - Each field hex
28 | # - Each field colon-terminated
29 | # - sk includes pk at end
30 | # - sm includes m at end
31 | path = os.path.join(os.path.dirname(__file__), "test_data", "ed25519")
32 | with codecs.open(path, "r", encoding="utf-8") as fp:
33 | for line in fp:
34 | x = line.split(":")
35 | yield (
36 | # Secret Key
37 | # Secret key is 32 bytes long, or 64 hex characters and has
38 | # public key appended to it
39 | x[0][0:64].encode("ascii"),
40 | # Public Key
41 | x[1].encode("ascii"),
42 | # Message
43 | x[2].encode("ascii"),
44 | # Signed Message
45 | x[3].encode("ascii"),
46 | # Signature Only
47 | # Signature comes from the Signed Message, it is 32 bytes long
48 | # and has the message appended to it
49 | binascii.hexlify(
50 | binascii.unhexlify(x[3].encode("ascii"))[:64]
51 | ),
52 | )
53 |
54 |
55 | @pytest.mark.parametrize(
56 | ("secret_key", "public_key", "message", "signed", "signature"),
57 | ed25519_known_answers(),
58 | )
59 | def test_ed25519_kat(secret_key, public_key, message, signed, signature):
60 | sk = binascii.unhexlify(secret_key)
61 | m = binascii.unhexlify(message)
62 |
63 | pk = ed25519.publickey_unsafe(sk)
64 | sig = ed25519.signature_unsafe(m, sk, pk)
65 |
66 | # Assert that the signature and public key are what we expected
67 | assert binascii.hexlify(pk) == public_key
68 | assert binascii.hexlify(sig) == signature
69 |
70 | # Validate the signature using the checkvalid routine
71 | ed25519.checkvalid(sig, m, pk)
72 |
73 | # Assert that we cannot forge a message
74 | try:
75 | if len(m) == 0:
76 | forgedm = b"x"
77 | else:
78 | forgedm = bytes([m[i] + (i == len(m) - 1) for i in range(len(m))])
79 | except ValueError:
80 | # TODO: Yes this means that we "pass" a test if we can't generate a
81 | # forged message. This matches the original test suite, it's
82 | # unclear if it was intentional there or not.
83 | pass
84 | else:
85 | with pytest.raises(ed25519.SignatureMismatch):
86 | ed25519.checkvalid(sig, forgedm, pk)
87 |
88 |
89 | def test_checkparams():
90 | # Taken from checkparams.py from DJB
91 | assert ed25519.b >= 10
92 | assert 8 * len(ed25519.H(b"hash input")) == 2 * ed25519.b
93 | assert pow(2, ed25519.q - 1, ed25519.q) == 1
94 | assert ed25519.q % 4 == 1
95 | assert pow(2, ed25519.l - 1, ed25519.l) == 1
96 | assert ed25519.l >= 2 ** (ed25519.b - 4)
97 | assert ed25519.l <= 2 ** (ed25519.b - 3)
98 | assert pow(ed25519.d, (ed25519.q - 1) // 2, ed25519.q) == ed25519.q - 1
99 | assert pow(ed25519.I, 2, ed25519.q) == ed25519.q - 1
100 | assert ed25519.isoncurve(ed25519.B)
101 | x, y, z, t = P = ed25519.scalarmult(ed25519.B, ed25519.l)
102 | assert ed25519.isoncurve(P)
103 | assert (x, y) == (0, z)
104 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py35,py36,py37,py38,py39,py310,py311,py312,pypy3,pep8
3 |
4 | [testenv]
5 | deps =
6 | pytest
7 | pytest-xdist
8 | commands = py.test -n 8
9 |
10 | [testenv:pep8]
11 | basepython = python3
12 | deps =
13 | flake8
14 | black
15 | commands =
16 | flake8 .
17 | black --check --target-version=py36 --line-length=79 .
18 |
19 | [flake8]
20 | exclude = .tox/
21 | ignore = E203,E741,W503
22 |
--------------------------------------------------------------------------------