├── .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 | --------------------------------------------------------------------------------