├── .gitignore ├── Ep ├── a ├── b ├── l ├── p ├── rigid ├── shape ├── x0 ├── x1 ├── y0 └── y1 ├── Eq ├── a ├── b ├── l ├── p ├── rigid ├── shape ├── x0 ├── x1 ├── y0 └── y1 ├── LICENSE ├── README.md ├── addchain_5inv.py ├── addchain_5inv_rtl.py ├── addchain_7inv.py ├── addchain_sqrt.py ├── amicable.sage ├── animation-p.webm ├── animation-q.webm ├── animation.sh ├── base_tables.sage ├── checksumsets.py ├── clean.sh ├── hashtocurve.sage ├── injectivitylemma.py ├── run.sh ├── sinsemilla.sage ├── squareroot.sage ├── squareroottab.sage ├── squareroottab16.sage ├── subgroupcheck.sage └── verify.sage /.gitignore: -------------------------------------------------------------------------------- 1 | verify-* 2 | expand2-* 3 | hex-* 4 | primes 5 | proof/ 6 | 7 | *.swp 8 | *.save 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | env/ 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | .hypothesis/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # dotenv 92 | .env 93 | 94 | # virtualenv 95 | .venv 96 | venv/ 97 | ENV/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | 112 | -------------------------------------------------------------------------------- /Ep/a: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /Ep/b: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /Ep/l: -------------------------------------------------------------------------------- 1 | 28948022309329048855892746252171976963363056481941647379679742748393362948097 2 | -------------------------------------------------------------------------------- /Ep/p: -------------------------------------------------------------------------------- 1 | 28948022309329048855892746252171976963363056481941560715954676764349967630337 2 | -------------------------------------------------------------------------------- /Ep/rigid: -------------------------------------------------------------------------------- 1 | fully rigid 2 | -------------------------------------------------------------------------------- /Ep/shape: -------------------------------------------------------------------------------- 1 | shortw 2 | -------------------------------------------------------------------------------- /Ep/x0: -------------------------------------------------------------------------------- 1 | -1 2 | -------------------------------------------------------------------------------- /Ep/x1: -------------------------------------------------------------------------------- 1 | -1 2 | -------------------------------------------------------------------------------- /Ep/y0: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /Ep/y1: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /Eq/a: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /Eq/b: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /Eq/l: -------------------------------------------------------------------------------- 1 | 28948022309329048855892746252171976963363056481941560715954676764349967630337 2 | -------------------------------------------------------------------------------- /Eq/p: -------------------------------------------------------------------------------- 1 | 28948022309329048855892746252171976963363056481941647379679742748393362948097 2 | -------------------------------------------------------------------------------- /Eq/rigid: -------------------------------------------------------------------------------- 1 | fully rigid 2 | -------------------------------------------------------------------------------- /Eq/shape: -------------------------------------------------------------------------------- 1 | shortw 2 | -------------------------------------------------------------------------------- /Eq/x0: -------------------------------------------------------------------------------- 1 | -1 2 | -------------------------------------------------------------------------------- /Eq/x1: -------------------------------------------------------------------------------- 1 | -1 2 | -------------------------------------------------------------------------------- /Eq/y0: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /Eq/y1: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The Zcash developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pallas/Vesta supporting evidence 2 | -------------------------------- 3 | 4 | This repository contains supporting evidence that the amicable pair of 5 | prime-order curves: 6 | 7 | * Ep : y^2 = x^3 + 5 over GF(p) of order q, called Pallas; 8 | * Eq : y^2 = x^3 + 5 over GF(q) of order p, called Vesta; 9 | 10 | with 11 | 12 | * p = 2^254 + 45560315531419706090280762371685220353 13 | * q = 2^254 + 45560315531506369815346746415080538113 14 | 15 | satisfy *some* of the [SafeCurves criteria](https://safecurves.cr.yp.to/index.html). 16 | 17 | The criteria that are *not* satisfied are, in summary: 18 | 19 | * large-magnitude CM discriminant (both curves have CM discriminant of absolute value 3, 20 | as a consequence of how they were constructed); 21 | * completeness (complete formulae are possible, but not according to the Safe curves 22 | criterion); 23 | * ladder support (not possible for prime-order curves); 24 | * Elligator 2 support (indistinguishability is possible using 25 | [Elligator Squared](https://ifca.ai/pub/fc14/paper_25.pdf), but not using Elligator 2); 26 | * twist security above 100 bits for Pallas. 27 | 28 | Pallas/Vesta is the first cycle output by 29 | ``sage amicable.sage --sequential --requireisos --sortpq --ignoretwist --nearpowerof2 255 32``. 30 | 31 | (The `--sequential` option makes the output completely deterministic and so resolves 32 | ambiguity about which result is "first". For exploratory searches it is faster not to 33 | use `--sequential`.) 34 | 35 | Prerequisites: 36 | 37 | * ``apt-get install sagemath`` 38 | 39 | Run ``sage verify.sage Ep`` and ``sage verify.sage Eq``; or ``./run.sh`` to run both 40 | and also print out the results. 41 | 42 | The output of ``amicable.sage`` with the above options includes isogenies of degree 3, 43 | suitable for use with the "simplified SWU" method for hashing to an elliptic curve. 44 | This is based on code from Appendix A of [Wahby and Boneh 2019](https://eprint.iacr.org/2019/403.pdf). 45 | 46 | To check the correctness of the endomorphism optimization described in the Halo paper, run 47 | ``python3 injectivitylemma.py`` and ``python3 checksumsets.py``. To also generate animations 48 | showing the minimum distances between multiples of ζ used in the proof, run ``./animation.sh``. 49 | 50 | ``animation.sh`` has the following prerequisites: 51 | 52 | * ``apt-get install ffmpeg ffcvt`` 53 | * ``pip3 install bintrees Pillow`` 54 | 55 | ``checksumsets.py`` on its own only requires the ``bintrees`` Python package. 56 | -------------------------------------------------------------------------------- /addchain_5inv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class Chain(object): 4 | SQR_COST = 0.8 5 | MUL_COST = 1 6 | 7 | def __init__(self): 8 | self.muls = 0 9 | self.sqrs = 0 10 | self.computed = set((1,)) 11 | 12 | def sqr(self, x, n=1): 13 | for i in range(n): 14 | assert(x in self.computed) 15 | self.sqrs += 1 16 | x = 2*x 17 | self.computed.add(x) 18 | 19 | return x 20 | 21 | def mul(self, x, y): 22 | assert(x in self.computed) 23 | assert(y in self.computed) 24 | assert(x != y) 25 | self.muls += 1 26 | r = x+y 27 | self.computed.add(r) 28 | return r 29 | 30 | def sqrmul(self, x, n, y): 31 | return self.mul(self.sqr(x, n), y) 32 | 33 | def cost(self): 34 | return self.sqrs*self.SQR_COST + self.muls*self.MUL_COST 35 | 36 | def __repr__(self): 37 | return "%dS + %dM (%.1f)" % (self.sqrs, self.muls, self.cost()) 38 | 39 | 40 | x_p = 0b11001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001101001110100111101110000011001001101000010000101001100000111000101110000011110000111100111111000011001100110011001100110011001101 41 | # a b c d e f g h i j k l m n o p q r s 42 | # 11001100110011 43 | # 111 1 44 | 45 | pc = Chain() 46 | p1 = 1 47 | p10 = pc.sqr(p1) 48 | p11 = pc.mul(p10, p1) 49 | p101 = pc.mul(p10, p11) 50 | p110 = pc.sqr(p11) 51 | p111 = pc.mul(p110, p1) 52 | p1001 = pc.mul(p111, p10) 53 | p1111 = pc.mul(p1001, p110) 54 | pr2 = pc.sqrmul(p110, 3, p11) 55 | pr4 = pc.sqrmul(pr2, 8, pr2) 56 | pr8 = pc.sqrmul(pr4, 16, pr4) 57 | pr16 = pc.sqrmul(pr8, 32, pr8) 58 | pr32 = pc.sqrmul(pr16, 64, pr16) 59 | pr32a = pc.sqrmul(pr32, 5, p1001) 60 | pr32b = pc.sqrmul(pr32a, 8, p111) 61 | pr32c = pc.sqrmul(pr32b, 4, p1) 62 | pr32d = pc.sqrmul(pr32c, 2, pr4) 63 | pr32e = pc.sqrmul(pr32d, 7, p11) 64 | pr32f = pc.sqrmul(pr32e, 6, p1001) 65 | pr32g = pc.sqrmul(pr32f, 3, p101) 66 | pr32h = pc.sqrmul(pr32g, 5, p1) 67 | pr32i = pc.sqrmul(pr32h, 7, p101) 68 | pr32j = pc.sqrmul(pr32i, 4, p11) 69 | pr32k = pc.sqrmul(pr32j, 8, p111) 70 | pr32l = pc.sqrmul(pr32k, 4, p1) 71 | pr32m = pc.sqrmul(pr32l, 4, p111) 72 | pr32n = pc.sqrmul(pr32m, 9, p1111) 73 | pr32o = pc.sqrmul(pr32n, 8, p1111) 74 | pr32p = pc.sqrmul(pr32o, 6, p1111) 75 | pr32q = pc.sqrmul(pr32p, 2, p11) 76 | pr32r = pc.sqrmul(pr32q, 34, pr8) 77 | pr32s = pc.sqrmul(pr32r, 2, p1) 78 | assert pr32s == x_p 79 | print(pc) 80 | 81 | x_q = 0b11001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001101001110100111101110000011001001101000010100001110111010010010101101011010011111001000101000000011001100110011001100110011001101 82 | # a b c d e f g h i j k l m n o p q r s t 83 | # 11001100110011 84 | # 111 1 85 | 86 | qc = Chain() 87 | q1 = 1 88 | q10 = qc.sqr(q1) 89 | q11 = qc.mul(q10, q1) 90 | q101 = qc.mul(q10, q11) 91 | q110 = qc.sqr(q11) 92 | q111 = qc.mul(q110, q1) 93 | q1001 = qc.mul(q111, q10) 94 | q1111 = qc.mul(q1001, q110) 95 | qr2 = qc.sqrmul(q110, 3, q11) 96 | qr4 = qc.sqrmul(qr2, 8, qr2) 97 | qr8 = qc.sqrmul(qr4, 16, qr4) 98 | qr16 = qc.sqrmul(qr8, 32, qr8) 99 | qr32 = qc.sqrmul(qr16, 64, qr16) 100 | qr32a = qc.sqrmul(qr32, 5, q1001) 101 | qr32b = qc.sqrmul(qr32a, 8, q111) 102 | qr32c = qc.sqrmul(qr32b, 4, q1) 103 | qr32d = qc.sqrmul(qr32c, 2, qr4) 104 | qr32e = qc.sqrmul(qr32d, 7, q11) 105 | qr32f = qc.sqrmul(qr32e, 6, q1001) 106 | qr32g = qc.sqrmul(qr32f, 3, q101) 107 | # diverges here 108 | qr32h = qc.sqrmul(qr32g, 7, q101) 109 | qr32i = qc.sqrmul(qr32h, 7, q111) 110 | qr32j = qc.sqrmul(qr32i, 4, q111) 111 | qr32k = qc.sqrmul(qr32j, 5, q1001) 112 | qr32l = qc.sqrmul(qr32k, 5, q101) 113 | qr32m = qc.sqrmul(qr32l, 3, q11) 114 | qr32n = qc.sqrmul(qr32m, 4, q101) 115 | qr32o = qc.sqrmul(qr32n, 3, q101) 116 | qr32p = qc.sqrmul(qr32o, 6, q1111) 117 | qr32q = qc.sqrmul(qr32p, 4, q1001) 118 | qr32r = qc.sqrmul(qr32q, 6, q101) 119 | qr32s = qc.sqrmul(qr32r, 37, qr8) 120 | qr32t = qc.sqrmul(qr32s, 2, q1) 121 | assert qr32t == x_q 122 | print(qc) 123 | 124 | -------------------------------------------------------------------------------- /addchain_5inv_rtl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class Chain(object): 4 | SQR_COST = 0.8 5 | MUL_COST = 1 6 | 7 | def __init__(self): 8 | self.muls = 0 9 | self.sqrs = 0 10 | self.computed = set((1,)) 11 | 12 | def sqr(self, x, n=1): 13 | for i in range(n): 14 | assert(x in self.computed) 15 | self.sqrs += 1 16 | x = 2*x 17 | self.computed.add(x) 18 | 19 | return x 20 | 21 | def mul(self, x, y): 22 | assert(x in self.computed) 23 | assert(y in self.computed) 24 | assert(x != y) 25 | self.muls += 1 26 | r = x+y 27 | self.computed.add(r) 28 | return r 29 | 30 | def sqrmul(self, x, n, y): 31 | return self.mul(self.sqr(x, n), y) 32 | 33 | def cost(self): 34 | return self.sqrs*self.SQR_COST + self.muls*self.MUL_COST 35 | 36 | def __repr__(self): 37 | return "%dS + %dM (%.1f)" % (self.sqrs, self.muls, self.cost()) 38 | 39 | 40 | # ---> up to here is a multiple of 0b110011 = 51 :-) 41 | x_p = 0b11001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001101001110100111101110000011001001101000010000101001100000111000101110000011110000111100111111000011001100110011001100110011001101 42 | 43 | pchain = Chain() 44 | pi = pa = 1 45 | for i in range(1, 128): 46 | pi = pchain.sqr(pi) 47 | if '01001110100111101110000011001001101000010000101001100000111000101110000011110000111100111111000011001100110011001100110011001101'[127-i] == '1': 48 | pa = pchain.mul(pa, pi) 49 | 50 | b = '1000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001' 51 | assert int(b, 2)*0b110011 == 0b110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011 52 | pj = pchain.sqrmul(pi, 1, pi) 53 | pk = pchain.sqrmul(pj, 4, pj) 54 | for k in range(1, 122): 55 | pk = pchain.sqr(pk) 56 | if b[121-k] == '1': 57 | pa = pchain.mul(pa, pk) 58 | 59 | assert pa == x_p, "\n" + format(pa, '0255b') + "\n" + format(x_p, '0255b') 60 | print(pchain) 61 | 62 | # ---> up to here is a multiple of 0b110011 = 51 :-) 63 | x_q = 0b11001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001101001110100111101110000011001001101000010100001110111010010010101101011010011111001000101000000011001100110011001100110011001101 64 | 65 | qchain = Chain() 66 | qi = qa = 1 67 | for i in range(1, 128): 68 | qi = qchain.sqr(qi) 69 | if '01001110100111101110000011001001101000010100001110111010010010101101011010011111001000101000000011001100110011001100110011001101'[127-i] == '1': 70 | qa = qchain.mul(qa, qi) 71 | 72 | b = '1000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001' 73 | assert int(b, 2)*0b110011 == 0b110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011 74 | qj = qchain.sqrmul(qi, 1, qi) 75 | qk = qchain.sqrmul(qj, 4, qj) 76 | for k in range(1, 122): 77 | qk = qchain.sqr(qk) 78 | if b[121-k] == '1': 79 | qa = qchain.mul(qa, qk) 80 | 81 | assert qa == x_q, "\n" + format(qa, '0255b') + "\n" + format(x_q, '0255b') 82 | print(qchain) 83 | -------------------------------------------------------------------------------- /addchain_7inv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class Chain(object): 4 | SQR_COST = 0.8 5 | MUL_COST = 1 6 | 7 | def __init__(self): 8 | self.muls = 0 9 | self.sqrs = 0 10 | self.computed = set((1,)) 11 | 12 | def sqr(self, x, n=1): 13 | for i in range(n): 14 | assert(x in self.computed) 15 | self.sqrs += 1 16 | x = 2*x 17 | self.computed.add(x) 18 | 19 | return x 20 | 21 | def mul(self, x, y): 22 | assert(x in self.computed) 23 | assert(y in self.computed) 24 | assert(x != y) 25 | self.muls += 1 26 | r = x+y 27 | self.computed.add(r) 28 | return r 29 | 30 | def sqrmul(self, x, n, y): 31 | return self.mul(self.sqr(x, n), y) 32 | 33 | def cost(self): 34 | return self.sqrs*self.SQR_COST + self.muls*self.MUL_COST 35 | 36 | def __repr__(self): 37 | return "%dS + %dM (%.1f)" % (self.sqrs, self.muls, self.cost()) 38 | 39 | 40 | pr48 = 0b101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101 41 | x_p = 0b10110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110111001111010101101111111110001111011101000101101110001101010111001101101100100000010001111000010010110110110110110110110110110111 42 | # | a b cx d e f g h i j k l m n o p q r s t u 43 | # 11 1111 111 44 | 45 | pc = Chain() 46 | p1 = 1 47 | p10 = pc.sqr(p1) 48 | p11 = pc.mul(p10, p1) 49 | p101 = pc.mul(p11, p10) 50 | p111 = pc.mul(p101, p10) 51 | p1001 = pc.mul(p111, p10) 52 | p1011 = pc.mul(p1001, p10) 53 | p1101 = pc.mul(p1011, p10) 54 | p1111 = pc.mul(p1101, p10) 55 | pr2 = pc.sqrmul(p1011, 2, p1) 56 | pr4 = pc.sqrmul(pr2, 6, pr2) 57 | pr8 = pc.sqrmul(pr4, 12, pr4) 58 | pr16 = pc.sqrmul(pr8, 24, pr8) 59 | pr32 = pc.sqrmul(pr16, 48, pr16) 60 | pr42a = pc.sqrmul(pr32, 35, p11) 61 | pr42b = pc.sqrmul(pr42a, 8, p1111) 62 | pr42c = pc.sqrmul(pr42b, 4, p111) 63 | pr42x = pc.sqrmul(pr42c, 1, pr16) 64 | pr42d = pc.sqrmul(pr42x, 4, p1111) 65 | pr42e = pc.sqrmul(pr42d, 3, p111) 66 | pr42f = pc.sqrmul(pr42e, 7, p1111) 67 | pr42g = pc.sqrmul(pr42f, 4, p111) 68 | pr42h = pc.sqrmul(pr42g, 2, p1) 69 | pr42i = pc.sqrmul(pr42h, 7, p1011) 70 | pr42j = pc.sqrmul(pr42i, 4, p111) 71 | pr42k = pc.sqrmul(pr42j, 7, p1101) 72 | pr42l = pc.sqrmul(pr42k, 5, p1011) 73 | pr42m = pc.sqrmul(pr42l, 4, p1001) 74 | pr42n = pc.sqrmul(pr42m, 6, pr2) 75 | pr42o = pc.sqrmul(pr42n, 4, p1001) 76 | pr42p = pc.sqrmul(pr42o, 7, p1) 77 | pr42q = pc.sqrmul(pr42p, 7, p1111) 78 | pr42r = pc.sqrmul(pr42q, 5, p1) 79 | pr42s = pc.sqrmul(pr42r, 26, pr8) 80 | pr42t = pc.sqrmul(pr42s, 6, pr2) 81 | pr42u = pc.sqrmul(pr42t, 2, p11) 82 | assert pr42u == x_p, format(pr42u, 'b') 83 | print(pc) 84 | 85 | 86 | qr48 = 0b101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101101 87 | x_q = 0b10110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110111001111010101101111111110001111011101001000111011000001110000101101000111101001100000110110000010110110110110110110110110110111 88 | # | a b cx d e f g h i j k l m n o p q r s t 89 | # 11 1111 111 90 | 91 | qc = Chain() 92 | q1 = 1 93 | q10 = qc.sqr(q1) 94 | q11 = qc.mul(q10, q1) 95 | q101 = qc.mul(q11, q10) 96 | q111 = qc.mul(q101, q10) 97 | q1001 = qc.mul(q111, q10) 98 | q1111 = qc.sqrmul(q111, 1, q1) 99 | qr2 = qc.sqrmul(q1001, 2, q1001) 100 | qr4 = qc.sqrmul(qr2, 6, qr2) 101 | qr8 = qc.sqrmul(qr4, 12, qr4) 102 | qr16 = qc.sqrmul(qr8, 24, qr8) 103 | qr32 = qc.sqrmul(qr16, 48, qr16) 104 | qr42a = qc.sqrmul(qr32, 35, q11) 105 | qr42b = qc.sqrmul(qr42a, 8, q1111) 106 | qr42c = qc.sqrmul(qr42b, 4, q111) 107 | qr42x = qc.sqrmul(qr42c, 1, qr16) 108 | qr42d = qc.sqrmul(qr42x, 4, q1111) 109 | qr42e = qc.sqrmul(qr42d, 3, q111) 110 | qr42f = qc.sqrmul(qr42e, 7, q1111) 111 | qr42g = qc.sqrmul(qr42f, 4, q111) 112 | # diverges here 113 | qr42h = qc.sqrmul(qr42g, 5, q1001) 114 | qr42i = qc.sqrmul(qr42h, 6, q111) 115 | qr42j = qc.sqrmul(qr42i, 3, q11) 116 | qr42k = qc.sqrmul(qr42j, 8, q111) 117 | qr42l = qc.sqrmul(qr42k, 10, qr2) 118 | qr42m = qc.sqrmul(qr42l, 7, q1111) 119 | qr42n = qc.sqrmul(qr42m, 2, q1) 120 | qr42o = qc.sqrmul(qr42n, 4, q11) 121 | qr42p = qc.sqrmul(qr42o, 7, q11) 122 | qr42q = qc.sqrmul(qr42p, 3, q11) 123 | qr42r = qc.sqrmul(qr42q, 29, qr8) 124 | qr42s = qc.sqrmul(qr42r, 6, qr2) 125 | qr42t = qc.sqrmul(qr42s, 2, q11) 126 | assert qr42t == x_q, format(qr42t, 'b') 127 | print(qc) 128 | 129 | -------------------------------------------------------------------------------- /addchain_sqrt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Addition chains needed for calculation of square roots on the Pasta fields: 4 | # 5 | # r = (m_p - 1)/2 for p = 1 + m_p * 2^32 6 | # s = (m_q - 1)/2 for q = 1 + m_q * 2^32 7 | 8 | class Chain(object): 9 | SQR_COST = 0.8 10 | MUL_COST = 1 11 | 12 | def __init__(self): 13 | self.muls = 0 14 | self.sqrs = 0 15 | self.computed = set((1,)) 16 | 17 | def sqr(self, x, n=1): 18 | for i in range(n): 19 | assert(x in self.computed) 20 | self.sqrs += 1 21 | x = 2*x 22 | self.computed.add(x) 23 | 24 | return x 25 | 26 | def mul(self, x, y): 27 | assert(x in self.computed) 28 | assert(y in self.computed) 29 | assert(x != y) 30 | self.muls += 1 31 | r = x+y 32 | self.computed.add(r) 33 | return r 34 | 35 | def sqrmul(self, x, n, y): 36 | return self.mul(self.sqr(x, n), y) 37 | 38 | def cost(self): 39 | return self.sqrs*self.SQR_COST + self.muls*self.MUL_COST 40 | 41 | def __repr__(self): 42 | return "%dS + %dM (%.1f)" % (self.sqrs, self.muls, self.cost()) 43 | 44 | 45 | p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 46 | q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 47 | n = 32 48 | assert(p % (1<> (n+1), 'b')) 52 | 53 | r = (1<<221) + 0b100010010001101001100011111100000010010100110011111001000110111001100100101101001100001110110 54 | # a b c d e f g h i j k l m n o p q r st 55 | 56 | assert(r == p >> (n+1)) 57 | rch = Chain() 58 | r1 = 1 59 | r10 = rch.sqr(r1) 60 | r11 = rch.mul(r10, r1) 61 | r110 = rch.sqr(r11) 62 | r111 = rch.mul(r110, r1) 63 | r1001 = rch.mul(r111, r10) 64 | r1101 = rch.mul(r111, r110) 65 | ra = rch.sqrmul(r1, 129, r1) 66 | rb = rch.sqrmul(ra, 7, r1001) 67 | rc = rch.sqrmul(rb, 7, r1101) 68 | rd = rch.sqrmul(rc, 4, r11) 69 | re = rch.sqrmul(rd, 6, r111) 70 | rf = rch.sqrmul(re, 3, r111) 71 | rg = rch.sqrmul(rf, 10, r1001) 72 | rh = rch.sqrmul(rg, 5, r1001) 73 | ri = rch.sqrmul(rh, 4, r1001) 74 | rj = rch.sqrmul(ri, 3, r111) 75 | rk = rch.sqrmul(rj, 4, r1001) 76 | rl = rch.sqrmul(rk, 5, r11) 77 | rm = rch.sqrmul(rl, 4, r111) 78 | rn = rch.sqrmul(rm, 4, r11) 79 | ro = rch.sqrmul(rn, 6, r1001) 80 | rp = rch.sqrmul(ro, 5, r1101) 81 | rq = rch.sqrmul(rp, 4, r11) 82 | rr = rch.sqrmul(rq, 7, r111) 83 | rs = rch.sqrmul(rr, 3, r11) 84 | rt = rch.sqr(rs) 85 | assert rt == r, format(rt, 'b') 86 | print(rch) 87 | 88 | 89 | # print(format(q >> (n+1), 'b')) 90 | 91 | s = (1<<221) + 0b100010010001101001100011111100000010011001010010101000110111011000110001000110111010110010000 92 | # a b c d e f g h i j k l m n o p q r s t 93 | # 1001 94 | # 1001 95 | 96 | assert(s == q >> (n+1)) 97 | sch = Chain() 98 | s1 = 1 99 | s10 = sch.sqr(s1) 100 | s11 = sch.mul(s10, s1) 101 | s111 = sch.sqrmul(s11, 1, s1) 102 | s1001 = sch.mul(s111, s10) 103 | s1011 = sch.mul(s1001, s10) 104 | s1101 = sch.mul(s1011, s10) 105 | sa = sch.sqrmul(s1, 129, s1) 106 | sb = sch.sqrmul(sa, 7, s1001) 107 | sc = sch.sqrmul(sb, 7, s1101) 108 | sd = sch.sqrmul(sc, 4, s11) 109 | se = sch.sqrmul(sd, 6, s111) 110 | sf = sch.sqrmul(se, 3, s111) 111 | sg = sch.sqrmul(sf, 10, s1001) 112 | sh = sch.sqrmul(sg, 4, s1001) 113 | si = sch.sqrmul(sh, 5, s1001) 114 | sj = sch.sqrmul(si, 5, s1001) 115 | sk = sch.sqrmul(sj, 3, s1001) 116 | sl = sch.sqrmul(sk, 4, s1011) 117 | sm = sch.sqrmul(sl, 4, s1011) 118 | sn = sch.sqrmul(sm, 5, s11) 119 | so = sch.sqrmul(sn, 4, s1) 120 | sp = sch.sqrmul(so, 5, s11) 121 | sq = sch.sqrmul(sp, 4, s111) 122 | sr = sch.sqrmul(sq, 5, s1011) 123 | ss = sch.sqrmul(sr, 3, s1) 124 | st = sch.sqr(ss, 4) 125 | assert st == s, format(st, 'b') 126 | print(sch) 127 | 128 | 129 | t = (1<<32) - 1 130 | 131 | assert(s == q >> (n+1)) 132 | tch = Chain() 133 | t1 = 1 134 | t2 = tch.sqrmul(t1, 1, t1) 135 | t4 = tch.sqrmul(t2, 2, t2) 136 | t8 = tch.sqrmul(t4, 4, t4) 137 | t16 = tch.sqrmul(t8, 8, t8) 138 | t32 = tch.sqrmul(t16, 16, t16) 139 | assert t32 == t, format(t32, 'b') 140 | print(tch) 141 | -------------------------------------------------------------------------------- /amicable.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | from multiprocessing import Pool, cpu_count 6 | from traceback import print_exc 7 | from itertools import combinations 8 | 9 | if sys.version_info[0] == 2: 10 | range = xrange 11 | from string import maketrans 12 | else: 13 | maketrans = str.maketrans 14 | 15 | 16 | # Let Ep/Fp : y^2 = x^3 + bp 17 | # Let Eq/Fq : y^2 = x^3 + bq 18 | 19 | # p and q should each be ~ L bits. 20 | 21 | DEFAULT_TWOADICITY = 32 22 | DEFAULT_STRETCH = 0 23 | 24 | COEFFICIENT_RANGE = (5,) 25 | #COEFFICIENT_RANGE = range(1, 100) 26 | 27 | GCD_PRIMES = (5, 7, 11, 13, 17) 28 | 29 | # Set to a prime, or 0 to disable searching for isogenies. 30 | ISOGENY_DEGREE_MAX = 3 31 | #ISOGENY_DEGREE_MAX = 37 32 | 33 | DEFAULT_TWIST_SECURITY = 120 34 | REQUIRE_PRIMITIVE = True 35 | REQUIRE_HALFZERO = True # nearpowerof2 strategy only 36 | 37 | 38 | # section 2: 39 | # [...] the order of a curve satisfying the norm equation 3V^2 = 4p - T^2 has one 40 | # of the six forms {p+1 +/- T, p+1 +/- (T +/- 3V)/2} [IEEE Std 1363-2000, section 41 | # A.14.2.3, item 6]. 42 | # 43 | # We choose 4p = 3V^2 + T^2, where (V-1)/2 and (T-1)/2 are both multiples of 2^twoadicity. 44 | # 45 | # Then 4p = (3(V-1)^2 + 6(V-1) + 3) + ((T-1)^2 + 2(T-1) + 1) 46 | # = 3(V-1)^2 + 6(V-1) + (T-1)^2 + 2(T-1) + 4 47 | # p = 3((V-1)/2)^2 + 3(V-1)/2 + ((T-1)/2)^2 + (T-1)/2 + 1 48 | # 49 | # So p-1 will be a multiple of 2^twoadicity, and so will q-1 for q in 50 | # { p + 1 - T, p + 1 + (T-3V)/2 }. 51 | # 52 | # We'd also like both p and q to be 1 (mod 6), so that we have efficient endomorphisms 53 | # on both curves. 54 | 55 | def low_hamming_order(L, twoadicity, wid, processes): 56 | Vlen = (L-1)//2 + 1 57 | Vbase = 1 << Vlen 58 | Tlen = (L-1)//4 59 | Tbase = 1 << Tlen 60 | trailing_zeros = twoadicity+1 61 | for w in range(wid, Tlen-trailing_zeros, processes): 62 | for Vc in combinations(range(trailing_zeros, Vlen), w): 63 | V = Vbase + sum([1 << i for i in Vc]) + 1 64 | assert(((V-1)/2) % (1<> trailing_zeros 82 | for Voffset in symmetric_range(100000, base=wid, step=processes): 83 | V = ((Vbase + Voffset) << trailing_zeros) + 1 84 | assert(((V-1)/2) % (1 << twoadicity) == 0) 85 | tmp = (1<<(L+1)) - 3*V^2 86 | if tmp < 0: continue 87 | Tbase = isqrt(tmp) >> trailing_zeros 88 | for Toffset in symmetric_range(100000): 89 | T = ((Tbase + Toffset) << trailing_zeros) + 1 90 | assert(((T-1)/2) % (1<>(L//2) != 1<<(L - 1 - L//2): 98 | continue 99 | 100 | if p > 1<<(L-1) and p % 6 == 1 and is_pseudoprime(p): 101 | yield (p, T, V) 102 | 103 | def symmetric_range(n, base=0, step=1): 104 | for i in range(base, n, step): 105 | yield -i 106 | yield i+1 107 | 108 | SWAP_SIGNS = maketrans("+-", "-+") 109 | 110 | def find_nice_curves(strategy, L, twoadicity, stretch, requireisos, sortpq, twistsec, wid, processes): 111 | for (p, T, V) in strategy(L, max(0, twoadicity-stretch), wid, processes): 112 | if p % (1<>(L//2) != 1<<(L - 1 - L//2): 121 | continue 122 | 123 | if q not in (p, p+1, p-1) and q > 1<<(L-1) and q % 6 == 1 and q % (1< Appendix A. 219 | # Look for isogenous curves having j-invariant not in {0, 1728}. 220 | for degree in primes(ISOGENY_DEGREE_MAX+1): 221 | sys.stdout.write('~') 222 | sys.stdout.flush() 223 | for iso in E.isogenies_prime_degree(degree): 224 | if iso.codomain().j_invariant() not in (0, 1728): 225 | return iso.dual() 226 | 227 | return None 228 | 229 | 230 | def format_weight(x, detail=True): 231 | X = format(abs(x), 'b') 232 | if detail: 233 | assert(X.endswith('1')) 234 | detailstr = " (bitlength %d, weight %d, 2-adicity %d)" % (len(X), sum([int(c) for c in X]), 235 | len(X) - len(X[:-1].rstrip('0'))) 236 | else: 237 | detailstr = " (bitlength %d)" % (len(X),) 238 | 239 | return "%s0b%s%s" % ("-" if x < 0 else "", X, detailstr) 240 | 241 | 242 | def main(): 243 | args = sys.argv[1:] 244 | strategy = near_powerof2_order if "--nearpowerof2" in args else low_hamming_order 245 | processes = 1 if "--sequential" in args else cpu_count() 246 | if processes >= 6: 247 | processes -= 2 248 | requireisos = "--requireisos" in args 249 | sortpq = "--sortpq" in args 250 | twistsec = 0 if "--ignoretwist" in args else DEFAULT_TWIST_SECURITY 251 | args = [arg for arg in args if not arg.startswith("--")] 252 | 253 | if len(args) < 1: 254 | print(""" 255 | Usage: sage amicable.sage [--sequential] [--requireisos] [--sortpq] [--ignoretwist] [--nearpowerof2] [ [ Both primes should have this minimum bit length. 264 | Both primes should have this minimum 2-adicity. 265 | Find more candidates, by filtering from 2-adicity smaller by this many bits. 266 | """) 267 | return 268 | 269 | L = int(args[0]) 270 | twoadicity = int(args[1]) if len(args) > 1 else DEFAULT_TWOADICITY 271 | stretch = int(args[2]) if len(args) > 2 else DEFAULT_STRETCH 272 | 273 | print("Using %d processes." % (processes,)) 274 | pool = Pool(processes=processes) 275 | 276 | try: 277 | for wid in range(processes): 278 | pool.apply_async(worker, (strategy, L, twoadicity, stretch, requireisos, sortpq, twistsec, wid, processes)) 279 | 280 | while True: 281 | sleep(1000) 282 | except (KeyboardInterrupt, SystemExit): 283 | pass 284 | finally: 285 | pool.terminate() 286 | 287 | def worker(*args): 288 | try: 289 | real_worker(*args) 290 | except (KeyboardInterrupt, SystemExit): 291 | pass 292 | except: 293 | print_exc() 294 | 295 | def real_worker(*args): 296 | for (p, q, bp, bq, zetap, zetaq, qdesc, primp, primq, secp, secq, twsecp, twsecq, 297 | embeddivp, embeddivq, twembeddivp, twembeddivq, iso_Ep, iso_Eq) in find_nice_curves(*args): 298 | output = "\n" 299 | output += "p = %s\n" % format_weight(p) 300 | output += "q = %s\n" % format_weight(q) 301 | output += " = %s\n" % qdesc 302 | output += "ζ_p = %s (mod p)\n" % format_weight(int(zetap), detail=False) 303 | output += "ζ_q = %s (mod q)\n" % format_weight(int(zetaq), detail=False) 304 | 305 | output += "Ep/Fp : y^2 = x^3 + %d\n" % (bp,) 306 | output += "Eq/Fq : y^2 = x^3 + %d\n" % (bq,) 307 | 308 | output += "gcd(p-1, α) = 1 for α ∊ {%s}\n" % (", ".join(map(str, find_gcd_primes(p))),) 309 | output += "gcd(q-1, α) = 1 for α ∊ {%s}\n" % (", ".join(map(str, find_gcd_primes(q))),) 310 | 311 | output += "%d is %ssquare and %sprimitive in Fp\n" % (bp, "" if Mod(bp, p).is_square() else "non", "" if primp else "non") 312 | output += "%d is %ssquare and %sprimitive in Fq\n" % (bq, "" if Mod(bq, q).is_square() else "non", "" if primq else "non") 313 | 314 | output += "Ep security = %.1f, embedding degree = (q-1)/%d\n" % (secp, embeddivp) 315 | output += "Eq security = %.1f, embedding degree = (p-1)/%d\n" % (secq, embeddivq) 316 | 317 | output += "Ep twist security = %.1f, embedding degree = (2p + 1 - q)/%d\n" % (twsecp, twembeddivp) 318 | output += "Eq twist security = %.1f, embedding degree = (2q + 1 - p)/%d\n" % (twsecq, twembeddivq) 319 | 320 | if iso_Ep is not None: 321 | output += "iso_Ep = %r\n" % (iso_Ep,) 322 | output += "iso_Ep maps = %r\n" % (iso_Ep.rational_maps(),) 323 | elif ISOGENY_DEGREE_MAX > 0: 324 | output += "No Ep isogenies for simplified SWU with degree ≤ %d\n" % (ISOGENY_DEGREE_MAX,) 325 | 326 | if iso_Eq is not None: 327 | output += "iso_Eq = %r\n" % (iso_Eq,) 328 | output += "iso_Eq maps = %r\n" % (iso_Eq.rational_maps(),) 329 | elif ISOGENY_DEGREE_MAX > 0: 330 | output += "No Eq isogenies for simplified SWU with degree ≤ %d\n" % (ISOGENY_DEGREE_MAX,) 331 | 332 | print(output) # one syscall to minimize tearing 333 | 334 | main() 335 | -------------------------------------------------------------------------------- /animation-p.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zcash/pasta/f0f7068552a3565786cb338448cb58bc36a8314a/animation-p.webm -------------------------------------------------------------------------------- /animation-q.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zcash/pasta/f0f7068552a3565786cb338448cb58bc36a8314a/animation-q.webm -------------------------------------------------------------------------------- /animation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # pip install bintrees Pillow 3 | # apt-get install ffmpeg ffcvt 4 | 5 | set -e 6 | ./checksumsets.py --animate 7 | if [ -f animation-p.gif ]; then 8 | ffcvt -f animation-p.gif 9 | mv -f animation-p_.webm animation-p.webm 10 | rm -f animation-p.gif 11 | fi 12 | if [ -f animation-q.gif ]; then 13 | ffcvt -f animation-q.gif 14 | mv -f animation-q_.webm animation-q.webm 15 | rm -f animation-q.gif 16 | fi 17 | -------------------------------------------------------------------------------- /base_tables.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | 3 | # Let's say we want to interpolate between h curve points (x, y) over a 4 | # curve y^2 = x^3 + b in a PLONK circuit, for small h. 5 | # The obvious way to do it involves 2h fixed columns: 6 | # use \sum\limits_{j=0}^{h-1} x_j . l_j to interpolate x, and similarly for y. 7 | # 8 | # We want to use only h+1 columns. Here's how: 9 | # - Interpolate x as above. 10 | # - Witness y and check y^2 = x^3 + b. 11 | # - Witness u such that u^2 = y+z. 12 | # 13 | # where z is some field element that "makes the signs come out right". 14 | # The purpose of this script is to find z. 15 | 16 | load('hashtocurve.sage') 17 | 18 | if sys.version_info[0] == 2: 19 | from string import maketrans 20 | else: 21 | maketrans = str.maketrans 22 | 23 | 24 | def hash_to_pallas(domain_prefix, msg): 25 | (P, _, _) = hash_to_pallas_jacobian(msg, domain_prefix + "-pallas_XMD:BLAKE2b_SSWU_RO_") 26 | return P 27 | 28 | def I2LEOSP_32(j): 29 | return pack(" (pip install bintrees) 6 | # (pip install Pillow), if --animate is used 7 | 8 | import sys 9 | from dataclasses import dataclass 10 | from typing import Optional 11 | from collections import deque 12 | from math import log 13 | 14 | 15 | # From the Halo paper: 16 | 17 | # Let A = [0, 2^{λ/2 + 1} + 2^{λ/2} - 1]. It is straightforward to verify that a, b ∈ A 18 | # at the end of Algorithm 3 for any input \mathbf{r}. 19 | # {In fact a, b ∈ [2^{λ/2} + 1, 2^{λ/2 + 1} + 2^{λ/2} - 1], but it is convenient to 20 | # define A to start at 0.} 21 | # 22 | # Next we need to show that the mapping (a ⦂ A, b ⦂ A) ↦ (a ζ_q + b) mod q is injective. 23 | # This will depend on the specific values of ζ_q and q, and can be cast as a sumset problem. 24 | # 25 | # We use the notation v·A + A for { (av + b) mod q : a, b ∈ A }, and A - A for -1·A + A. 26 | # {We take sumsets using this notation to implicitly be subsets of F_q.} 27 | # The question is then whether |ζ_q·A + A| = |A|^2. 28 | # 29 | # For intuition, note that if av + b = a'v + b' (mod q), with a ≠ a', we would have 30 | # v = (b' - b)/(a - a') (mod q). Thus the number of v ∈ F_q for which |v·A + A| < |A|^2 31 | # is at most (|A - A| - 1)^2. {We thank Robert Israel for this observation. [HI2019]} 32 | # Since in our case (|A - A| - 1)^2 ≈ 9·2^130 is small compared to q ≈ 2^254, we would 33 | # heuristically expect that |ζ_q·A + A| = |A|^2 unless there is some reason why ζ_q does 34 | # not "behave like a random element of F_q". 35 | # 36 | # Of course ζ_q is *not* a random element of F_q, and so the above argument can only be 37 | # used for intuition. Even when (|A - A| - 1)^2 is small compared to q, there are clearly 38 | # values of ζ_q and q for which it would not hold. To prove that it holds in the needed 39 | # cases for the Tweedledum and Tweedledee curves used in our implementation, we take a 40 | # different tack. 41 | # 42 | # Define a distance metric δ_q on F_q so that δ_q(x, y) is the minimum distance between 43 | # x and y around the ring of integers modulo q in either direction, i.e. 44 | # 45 | # δ_q(x, y) = min(z, q - z) where z = (x - y) mod q 46 | # 47 | # Now let D_{q,ζ_q}(m) be the minimum δ_q-distance between any two elements of ζ_q·[0, m], 48 | # i.e. 49 | # 50 | # D_{q,ζ_q}(m) = min{ δ_q(a ζ_q, a' ζ_q ) : a, a' ∈ [0, m] } 51 | # 52 | # An algorithm to compute D_{q,ζ_q}(m) is implemented by checksumsets.py in [Hopw2019] 53 | # [i.e. this file]; it works by iteratively finding each m at which D_{q,ζ_q}(m) 54 | # decreases. [...] 55 | # 56 | # Now if D_{q,ζ_q}(2^{λ/2 + 1} + 2^{λ/2} - 1) ≥ 2^{λ/2 + 1} + 2^{λ/2}, then copies of 57 | # A will "fit within the gaps" in ζ_q·A. That is, ζ_q·A + A will have |A|^2 elements, 58 | # because all of the sets { ζ_q·{a} + A : a ∈ A } will be disjoint. 59 | # 60 | # The algorithm is based on the observation that the problem of deciding when 61 | # D_{q,ζ_q}(m) next decreases is self-similar to deciding when it first decreases. 62 | # It computes the exact min-distance at each decrease (not just a lower bound), 63 | # which facilitates detecting any bugs in the algorithm. Also, we check correctness 64 | # of the partial results up to a given bound on m, against a naive algorithm. 65 | 66 | BRUTEFORCE_THRESHOLD = 100000 67 | 68 | DEBUG = False 69 | 70 | @dataclass 71 | class State: 72 | u: int 73 | m: int 74 | n: int 75 | d: int 76 | 77 | 78 | def D(q, zeta, mm, animator=None): 79 | if DEBUG: print("(q, zeta, mm) =", (q, zeta, mm)) 80 | Dcheck = [] if BRUTEFORCE_THRESHOLD == 0 else bruteforce_D(q, zeta, min(mm, BRUTEFORCE_THRESHOLD)) 81 | 82 | # (u + am) : a ∈ Nat is the current arithmetic progression 83 | # n is the previous min-distance 84 | # d is the current min-distance 85 | cur = State(u=0, m=1, n=q, d=zeta) 86 | old = None 87 | 88 | while True: 89 | # Consider values of x where D_{q,ζ_q}(x) decreases, i.e. where 90 | # D_{q,ζ_q}(x) < D_{q,ζ_q})(x-1). 91 | # 92 | # We keep track of an arithmetic progression (u + am) such that the next 93 | # value at which D_{q,ζ_q}(x) decreases will be for x in this progression, 94 | # at the point at which xζ gets close to (but not equal to) 0. 95 | # 96 | # TODO: explain why the target is always 0. 97 | # 98 | # If we set s = floor(n/d), then D_{q,ζ_q}(x) can decrease at a = s 99 | # and potentially also at a = s+1. 100 | assert (cur.m*zeta) % q in (cur.d, q - cur.d) 101 | s = cur.n // cur.d 102 | x0 = cur.u + s*cur.m 103 | d0 = cur.n % cur.d 104 | if DEBUG: print("\n(x0, d0, cur, s) =", (x0, d0, cur, s)) 105 | assert dist(0, x0*zeta, q) == d0 106 | if x0-1 < len(Dcheck): assert Dcheck[x0-1] == cur.d 107 | if x0 > mm: 108 | if animator is not None: 109 | if DEBUG: print("(q, zeta, old, cur, s) =", (q, zeta, old, cur, s)) 110 | animator.render(q, zeta, old, cur, None, s) 111 | return cur.d 112 | if x0 < len(Dcheck): assert Dcheck[x0] == d0 113 | 114 | x1 = cur.u + (s+1)*cur.m 115 | d1 = (s+1)*cur.d - cur.n 116 | if d1 < d0: 117 | if DEBUG: print("(x1, d1, cur, s+1) =", (x1, d1, cur, s+1)) 118 | assert dist(0, x1*zeta, q) == d1 119 | if x1-1 < len(Dcheck): assert Dcheck[x1-1] == d0 120 | if x1 > mm: 121 | if animator is not None: 122 | if DEBUG: print("(q, zeta, old, cur, s+1) =", (q, zeta, old, cur, s+1)) 123 | animator.render(q, zeta, old, cur, None, s+1) 124 | return d0 125 | if x1 < len(Dcheck): assert Dcheck[x1] == d1 126 | 127 | # This is the case where the smaller new distance is past zero. 128 | # The next iteration should consider the region of size d0 starting at x = x0 129 | # (i.e. just before we went past zero) and increasing by x1, i.e. dividing 130 | # that region by intervals of d1. 131 | new = State(u=x0, m=x1, n=d0, d=d1) 132 | else: 133 | # This is the case where the smaller new distance is short of zero. 134 | # The next iteration should check the region of size cur.d - d0 starting at x = x1 135 | # (i.e. the wraparound past zero) and increasing by x0, i.e. dividing that 136 | # region by intervals of d0. 137 | new = State(u=x1, m=x0, n=cur.d - d0, d=d0) 138 | 139 | assert dist(0, new.u*zeta, q) in (new.n, q - new.n) 140 | #if dist(0, new.u*zeta, q) != new.n: print("hmm") 141 | 142 | if animator is not None: 143 | animator.render(q, zeta, old, cur, new, s) 144 | 145 | (old, cur) = (cur, new) 146 | 147 | 148 | def bruteforce_D(q, zeta, mm): 149 | # Can't use sortedcontainers because its data structures are backed by 150 | # lists-of-lists, not trees. We must have O(log n) insert, prev, and succ. 151 | from bintrees import RBTree as sortedset 152 | 153 | resD = deque([zeta]) 154 | lastd = zeta 155 | S = sortedset() 156 | S.insert(0, None) 157 | S.insert(q, None) 158 | for x in range(1, mm+1): 159 | v = (x*zeta) % q 160 | S.insert(v, None) 161 | vp = S.prev_key(v) 162 | vs = S.succ_key(v) 163 | d = min(v-vp, vs-v) 164 | resD.append(d) 165 | #if DEBUG and d < lastd: print((x, d)) 166 | lastd = d 167 | 168 | return list(resD) 169 | 170 | def dist(x, y, q): 171 | z = (x-y+q) % q 172 | return min(z, q-z) 173 | 174 | def signed_mod(x, q): 175 | r = x % q 176 | return r if r <= q//2 else r-q 177 | 178 | 179 | class Animator: 180 | fontfile = '/usr/share/texlive/texmf-dist/fonts/truetype/google/roboto/Roboto-Regular.ttf' 181 | 182 | frame_duration = 20 # ms 183 | pause_frames = 35 184 | zoom_frames = 45 185 | 186 | width = 800 # pixels 187 | height = 400 # pixels 188 | oversample = 3 189 | line_halfwidth = 1 # subpixels 190 | 191 | ground_colour = '#ffffff' # white 192 | scale_colour = '#0000a0' # blue 193 | midline_colour = '#c00000' # red 194 | old_colour = '#a0a0a0' # grey 195 | cur_colour = '#000000' # black 196 | new_colour = '#008000' # green 197 | final_colour = '#c00000' # red 198 | 199 | def __init__(self, name): 200 | # We don't want to depend on PIL unless an Animator is instantiated. 201 | from PIL import Image, ImageDraw, ImageColor, ImageFont 202 | self.Image = Image 203 | self.ImageDraw = ImageDraw 204 | self.ImageColor = ImageColor 205 | 206 | self.font = ImageFont.truetype(self.fontfile, 20*self.oversample, index=0, encoding='unic') 207 | self.font_super = ImageFont.truetype(self.fontfile, 12*self.oversample, index=0, encoding='unic') 208 | self.images = deque() 209 | self.name = name 210 | 211 | def render(self, q, zeta, old, cur, new, s): 212 | sys.stdout.write(':') 213 | sys.stdout.flush() 214 | 215 | n = min(cur.n, q//2) 216 | for aa in range(1, s+1): 217 | self.render_zoomed(q, zeta, old, cur, None, aa, n) 218 | 219 | if new is None: 220 | self.render_zoomed(q, zeta, old, cur, new, s, n, final=True) 221 | return 222 | 223 | self.render_zoomed( q, zeta, old, cur, new, s+1, n, frames=self.pause_frames) 224 | 225 | step = (1.0*n/new.n - 1.0)/self.zoom_frames 226 | for zoom in range(1, self.zoom_frames): 227 | n_scale = int(n/(1.0 + zoom*step)) 228 | self.render_zoomed(q, zeta, old, cur, new, s+1, n_scale) 229 | 230 | self.render_zoomed( q, zeta, old, cur, new, s+1, new.n, frames=self.pause_frames) 231 | 232 | def render_zoomed(self, q, zeta, old, cur, new, aa, n_scale, frames=1, final=False): 233 | px = self.oversample 234 | lx = self.line_halfwidth 235 | (w, h) = (self.width * px, self.height * px) 236 | scale = (w/2)/n_scale 237 | xmid = w//2 238 | ymid = (40*px + h)//2 239 | 240 | image = self.Image.new('RGB', (w, h), color=self.ground_colour) 241 | image.convert('P') 242 | draw = self.ImageDraw.Draw(image) 243 | 244 | bits = int(log(n_scale, 2)) 245 | for tick in range(bits-3, bits+1): 246 | xoff = int(scale*(1<= limit else '<'), limit) 305 | 306 | if animator is not None: 307 | animator.save() 308 | 309 | assert Dq >= limit 310 | 311 | 312 | def main(): 313 | args = sys.argv[1:] 314 | if "--help" in args: 315 | print("Usage: checksumsets.py [--animate]") 316 | return 317 | 318 | halflambda = 64 319 | limit = 3< and 4 | # . 5 | 6 | import sys 7 | from math import ceil, log 8 | from struct import pack 9 | from pyblake2 import blake2b 10 | from hashlib import sha256 11 | 12 | if sys.version_info[0] == 2: 13 | range = xrange 14 | as_byte = ord 15 | else: 16 | as_byte = lambda x: x 17 | 18 | def as_bytes(x): 19 | # 20 | return bytes(bytearray(x)) 21 | 22 | load('squareroottab.sage') 23 | 24 | DEBUG = True 25 | VERBOSE = False 26 | OP_COUNT = False 27 | 28 | # E: a short Weierstrass elliptic curve 29 | def find_z_sswu(E): 30 | (0, 0, 0, A, B) = E.a_invariants() 31 | F = E.base_field() 32 | 33 | R. = F[] # Polynomial ring over F 34 | g = x^3 + F(A) * x + F(B) # y^2 = g(x) = x^3 + A * x + B 35 | 36 | # is 37 | # ambiguous about whether to start with ctr = F.gen() or ctr = 1. In fact they are specified to be the same, 38 | # since F is a prime field. 39 | # , 40 | # The note in the I-D ("NOTE: if init_ctr=1 fails to find Z, try setting it to F.gen()") could only make a 41 | # difference for extension fields that are constructed with an explicit modulus. 42 | ctr = 1 43 | while True: 44 | for Z_cand in (F(ctr), F(-ctr)): 45 | if is_good_Z(F, g, A, B, Z_cand): 46 | return Z_cand 47 | ctr += 1 48 | 49 | def is_good_Z(F, g, A, B, Z): 50 | # Criterion 1: Z is non-square in F. 51 | if Z.is_square(): 52 | return False 53 | 54 | # Criterion 2: Z != -1 in F. 55 | if Z == F(-1): 56 | return False 57 | 58 | # Criterion 3: g(x) - Z is irreducible over F. 59 | if not (g - Z).is_irreducible(): 60 | return False 61 | 62 | # Criterion 4: g(B / (Z * A)) is square in F. 63 | if not g(F(B) / (Z * F(A))).is_square(): 64 | return False 65 | 66 | return True 67 | 68 | 69 | # Point in Chudnovsky coordinates (Jacobian with Z^2 and Z^3 cached). 70 | class ChudnovskyPoint: 71 | def __init__(self, E, x, y, z, z2=None, z3=None): 72 | if z2 is None: 73 | z2 = z^2 74 | if z3 is None: 75 | z3 = z^3 76 | 77 | if DEBUG: 78 | (0, 0, 0, A, B) = E.a_invariants() 79 | assert z2 == z^2 80 | assert z3 == z^3 81 | assert y^2 == x^3 + A*x*z^4 + B*z^6 82 | 83 | (self.x, self.y, self.z, self.z2, self.z3) = (x, y, z, z2, z3) 84 | 85 | def add(self, other, E, c): 86 | (0, 0, 0, A, B) = E.a_invariants() 87 | 88 | # Unified addition on y^2 = x^3 + Ax + B with Chudnovsky input and output. 89 | (X1, Y1, Z1, Z1_2, Z1_3) = ( self.x, self.y, self.z, self.z2, self.z3) 90 | (X2, Y2, Z2, Z2_2, Z2_3) = (other.x, other.y, other.z, other.z2, other.z3) 91 | 92 | assert Z1 != 0 and Z2 != 0 93 | 94 | # 95 | U1 = c.mul(X1, Z2_2) 96 | U2 = c.mul(X2, Z1_2) 97 | S1 = c.mul(Y1, Z2_3) 98 | S2 = c.mul(Y2, Z1_3) 99 | H = U2 - U1 100 | 101 | # now unify doubling : 102 | # XX = c.sqr(X) 103 | # YY = c.sqr(Y) 104 | # YYYY = c.sqr(YY) 105 | # S = 2*(c.sqr(X1 + YY) - XX - YYYY) 106 | # = 4*X1*YY 107 | # M = 3*XX + c.mul(A, Z1_2) 108 | # X3 = c.sqr(M) - 2*S 109 | # Y3 = M*(S - X3) - 8*YYYY 110 | # Z3 = c.sqr(Y1 + Z1) - YY - ZZ 111 | # 112 | # with the rest of addition: 113 | # I = c.sqr(2*H) 114 | # J = c.mul(H, I) 115 | # r = 2*(S2-S1) 116 | # V = c.mul(U1, I) 117 | # X3 = c.sqr(r) - J - 2*V 118 | # Y3 = r*(V - X3) - 2*c.mul(S1, J) 119 | # Z3 = c.mul((c.sqr(Z1 + Z2) - Z1_2 - Z2_2), H) 120 | 121 | r = 2*(S2-S1) 122 | X_or_r = select_z_nz(H, X1, r) 123 | Y_or_H = select_z_nz(H, Y1, H) 124 | XX_or_rr = c.sqr(X_or_r) 125 | if DEBUG: assert XX_or_rr == X1^2 if H == 0 else r^2 126 | 127 | YY_or_HH = c.sqr(Y_or_H) 128 | if DEBUG: assert YY_or_HH == Y1^2 if H == 0 else H^2 129 | 130 | YYY_or_HHH = c.mul(Y_or_H, YY_or_HH) 131 | if DEBUG: assert YYY_or_HHH == Y1^3 if H == 0 else H^3 132 | 133 | YYYY_or_S1HHH = c.mul(select_z_nz(H, Y_or_H, S1), YYY_or_HHH) 134 | if DEBUG: assert YYYY_or_S1HHH == Y1^4 if H == 0 else S1*H^3 135 | 136 | S_or_V = 4*c.mul(select_z_nz(H, X1, U1), YY_or_HH) 137 | if DEBUG: assert S_or_V == 4*X1*Y1^2 if H == 0 else U1 * (4*H^3) 138 | 139 | J = select_z_nz(H, 0, 4*YYY_or_HHH) 140 | # W = { (Z + Y)^2 - Z^2 = 2*Y*Z + Y^2 for doubling 141 | # { (Z1 + Z2)^2 - Z1^2 = 2*Z1*Z2 + Z2^2 for addition 142 | W = c.sqr(Z1 + select_z_nz(H, Y1, Z2)) - Z1_2 143 | if DEBUG: assert W == 2*Y1*Z1 + Y1^2 if H == 0 else 2*Z1*Z2 + Z2^2 144 | 145 | # Another option would be to multiply Z1_2 by A if that's faster than squaring. 146 | Z1_4 = c.sqr(Z1_2) 147 | AZ1_4_or_Z3 = c.mul(select_z_nz(H, A, H), select_z_nz(H, Z1_4, W - Z2_2)) 148 | if DEBUG: assert AZ1_4_or_Z3 == A*Z1^4 if H == 0 else 2*Z1*Z2*H 149 | 150 | M_or_r = select_z_nz(H, 3*XX_or_rr + AZ1_4_or_Z3, r) 151 | if DEBUG: assert M_or_r == 3*X1^2 + A*Z1^4 if H == 0 else r 152 | 153 | X3 = c.sqr(M_or_r) - J - 2*S_or_V 154 | Y3 = c.mul(M_or_r, S_or_V - X3) - 8*YYYY_or_S1HHH 155 | # If U1 + U2 = 0 then the result is the point at infinity. 156 | Z3 = select_z_nz(U1 + U2, 0, select_z_nz(H, W - YY_or_HH, AZ1_4_or_Z3)) 157 | Z3_2 = c.sqr(Z3) 158 | Z3_3 = c.mul(Z3_2, Z3) 159 | R = ChudnovskyPoint(E, X3, Y3, Z3, Z3_2, Z3_3) 160 | 161 | if DEBUG: assert R.to_sage(E) == self.to_sage(E) + other.to_sage(E) 162 | return R 163 | 164 | def to_sage(self, E): 165 | return E((self.x / self.z2, self.y / self.z3)) 166 | 167 | def to_affine(self, E): 168 | return self.to_sage(E).xy() 169 | 170 | def to_jacobian(self): 171 | return (self.x, self.y, self.z) 172 | 173 | def __repr__(self): 174 | return "ChudnovskyPoint {\n 0x%064x\n: 0x%064x\n: 0x%064x\n: 0x%064x\n: 0x%064x\n}" % (int(self.x), int(self.y), int(self.z), int(self.z2), int(self.z3)) 175 | 176 | 177 | assert p == 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 178 | assert q == 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 179 | Fp = GF(p) 180 | Fq = GF(q) 181 | 182 | IsoEp_A = 10949663248450308183708987909873589833737836120165333298109615750520499732811 183 | IsoEq_A = 17413348858408915339762682399132325137863850198379221683097628341577494210225 184 | IsoEp_B = 1265 185 | IsoEq_B = 1265 186 | IsoEp = EllipticCurve(Fp, [IsoEp_A, IsoEp_B]) 187 | IsoEq = EllipticCurve(Fq, [IsoEq_A, IsoEq_B]) 188 | Ep = EllipticCurve(Fp, [0, 5]) 189 | Eq = EllipticCurve(Fq, [0, 5]) 190 | 191 | k = 256 192 | Lp = (len(format(p, 'b')) + k + 7) // 8 193 | Lq = (len(format(q, 'b')) + k + 7) // 8 194 | assert Lp == 64 and Lq == 64 195 | CHUNKLEN = Lp 196 | 197 | IsoEpZ = find_z_sswu(IsoEp) 198 | IsoEqZ = find_z_sswu(IsoEq) 199 | assert IsoEpZ == Mod(-13, p) 200 | assert IsoEqZ == Mod(-13, q) 201 | 202 | 203 | def select_z_nz(s, ifz, ifnz): 204 | # This should be constant-time in a real implementation. 205 | return ifz if (s == 0) else ifnz 206 | 207 | def map_to_curve_simple_swu(F, E, Z, u, c): 208 | if VERBOSE: print("map_to_curve(0x%064x)" % (u,)) 209 | 210 | # would be precomputed 211 | h = F.g 212 | (0, 0, 0, A, B) = E.a_invariants() 213 | mBdivA = -B / A 214 | BdivZA = B / (Z * A) 215 | Z2 = Z^2 216 | assert (Z/h).is_square() 217 | theta = sqrt(Z/h) 218 | 219 | # 1. tv1 = inv0(Z^2 * u^4 + Z * u^2) 220 | # 2. x1 = (-B / A) * (1 + tv1) 221 | # 3. If tv1 == 0, set x1 = B / (Z * A) 222 | # 4. gx1 = x1^3 + A * x1 + B 223 | # 224 | # We use the "Avoiding inversions" optimization in [WB2019, section 4.2] 225 | # (not to be confused with section 4.3): 226 | # 227 | # here [WB2019] 228 | # ------- --------------------------------- 229 | # Z \xi 230 | # u t 231 | # Z * u^2 \xi * t^2 (called u, confusingly) 232 | # x1 X_0(t) 233 | # x2 X_1(t) 234 | # gx1 g(X_0(t)) 235 | # gx2 g(X_1(t)) 236 | # 237 | # Using the "here" names: 238 | # x1 = N_x1/D = [B*(Z^2 * u^4 + Z * u^2 + 1)] / [-A*(Z^2 * u^4 + Z * u^2] 239 | # gx1 = U/V = [N_x1^3 + A * N_x1 * D^2 + B * D^3] / D^3 240 | 241 | # Z and B are small so we don't count multiplication by them as a mul; A is large. 242 | Zu2 = Z * c.sqr(u) 243 | ta = c.sqr(Zu2) + Zu2 244 | N_x1 = B * (ta + 1) 245 | D = c.mul(A, select_z_nz(ta, Z, -ta)) 246 | N2_x1 = c.sqr(N_x1) 247 | D2 = c.sqr(D) 248 | D3 = c.mul(D2, D) 249 | U = c.mul(N2_x1 + c.mul(A, D2), N_x1) + B*D3 250 | 251 | if DEBUG: 252 | x1 = N_x1/D 253 | gx1 = U/D3 254 | tv1 = (0 if ta == 0 else 1/ta) 255 | assert x1 == (BdivZA if tv1 == 0 else mBdivA * (1 + tv1)) 256 | assert gx1 == x1^3 + A * x1 + B 257 | 258 | # 5. x2 = Z * u^2 * x1 259 | N_x2 = c.mul(Zu2, N_x1) # same D 260 | 261 | # 6. gx2 = x2^3 + A * x2 + B [optimized out; see below] 262 | # 7. If is_square(gx1), set x = x1 and y = sqrt(gx1) 263 | # 8. Else set x = x2 and y = sqrt(gx2) 264 | (y1, zero_if_gx1_square) = F.sarkar_divsqrt(U, D3, c) 265 | if VERBOSE: print("zero_if_gx1_square = %064x" % (zero_if_gx1_square,)) 266 | 267 | # This magic also comes from a generalization of [WB2019, section 4.2]. 268 | # 269 | # The Sarkar square root algorithm with input s gives us a square root of 270 | # h * s for free when s is not square, where h is a fixed nonsquare. 271 | # We know that Z/h is a square since both Z and h are nonsquares. 272 | # Precompute \theta as a square root of Z/h, or choose Z = h so that \theta = 1. 273 | # 274 | # We have gx2 = g(Z * u^2 * x1) = Z^3 * u^6 * gx1 275 | # = (Z * u^3)^2 * (Z/h * h * gx1) 276 | # = (Z * \theta * u^3)^2 * (h * gx1) 277 | # 278 | # When gx1 is not square, y1 is a square root of h * gx1, and so Z * \theta * u^3 * y1 279 | # is a square root of gx2. Note that we don't actually need to compute gx2. 280 | 281 | y2 = c.mul(theta, c.mul(Zu2, c.mul(u, y1))) 282 | if DEBUG and zero_if_gx1_square != 0: 283 | x2 = N_x2/D 284 | assert y1^2 == h * gx1, (y1_2, Z, gx1) 285 | assert y2^2 == x2^3 + A * x2 + B, (y2, x2, A, B) 286 | 287 | N_x = select_z_nz(zero_if_gx1_square, N_x1, N_x2) 288 | y = select_z_nz(zero_if_gx1_square, y1, y2) 289 | 290 | # 9. If sgn0(u) != sgn0(y), set y = -y 291 | y = select_z_nz((int(u) % 2) - (int(y) % 2), y, -y) 292 | 293 | if VERBOSE: 294 | print("num_x = 0x%064x\ndiv = 0x%064x\ny = 0x%064x\ndiv3 = 0x%064x" % (int(N_x), int(D), int(y), int(D3))) 295 | 296 | return ChudnovskyPoint(E, c.mul(N_x, D), c.mul(y, D3), D, D2, D3) 297 | 298 | 299 | # iso_Ep = Isogeny of degree 3 from Elliptic Curve defined by y^2 = x^3 + 10949663248450308183708987909873589833737836120165333298109615750520499732811*x + 1265 over Fp 300 | def isop_map_affine(x, y, c): 301 | c.muls += 2+1+1 + 2+1+1+2 302 | # batch inversion 303 | c.muls += 3 304 | c.invs += 1 305 | Nx = ((( 6432893846517566412420610278260439325191790329320346825767705947633326140075 *x + 306 | 23989696149150192365340222745168215001509815558210986772351135915822265203574)*x + 307 | 10492611921771203378452795982353351666191589197598957448093274638589204800759)*x + 308 | 12865787693035132824841220556520878650383580658640693651535411895266652280192) 309 | Dx = (( x + 310 | 13271109177048389296812780941310096270046944650307955939477485891950613419807)*x + 311 | 22768321103861051515190775253992702316905399997697804654926324362758820947460) 312 | 313 | Ny = (((11793638718615538422771118843477472096184948937087302513907460903994431256804 *x + 314 | 11994848074575096182670111372584107500754907779105493386175567957911132601787)*x + 315 | 28823569610051396102362669851238297121581474897215657071023781420043761726004)*x + 316 | 1072148974419594402070101713043406554198631721553391137627950991272221023311) * y 317 | Dy = ((( x + 318 | 5432652610908059517272798285879155923388888734491153551238890455750936314542)*x + 319 | 10408918692925056833786833257634153023990087029210292532869619559576527581706)*x + 320 | 28948022309329048855892746252171976963363056481941560715954676764349967629797) 321 | 322 | return (Nx / Dx, Ny / Dy) 323 | 324 | # The same isogeny iso_Ep but with input in Chudnovsky coordinates (Jacobian with z^2 and z^3) 325 | # and output in Jacobian , 326 | # according to "Avoiding inversions" in [WB2019, section 4.3]. 327 | def isop_map_jacobian(P, c): 328 | (x, y, z, z2, z3) = (P.x, P.y, P.z, P.z2, P.z3) 329 | z4 = c.sqr(z2) 330 | z6 = c.sqr(z3) 331 | 332 | if VERBOSE: print("IsoEp { x: 0x%064x, y: 0x%064x, z: 0x%064x }" % (x, y, z)) 333 | Nx = ((( 6432893846517566412420610278260439325191790329320346825767705947633326140075 *x + 334 | 23989696149150192365340222745168215001509815558210986772351135915822265203574*z2)*x + 335 | 10492611921771203378452795982353351666191589197598957448093274638589204800759*z4)*x + 336 | 12865787693035132824841220556520878650383580658640693651535411895266652280192*z6) 337 | c.muls += 6 338 | Dx = (( z2 *x + 339 | 13271109177048389296812780941310096270046944650307955939477485891950613419807*z4)*x + 340 | 22768321103861051515190775253992702316905399997697804654926324362758820947460*z6) 341 | c.muls += 4 342 | 343 | Ny = (((11793638718615538422771118843477472096184948937087302513907460903994431256804 *x + 344 | 11994848074575096182670111372584107500754907779105493386175567957911132601787*z2)*x + 345 | 28823569610051396102362669851238297121581474897215657071023781420043761726004*z4)*x + 346 | 1072148974419594402070101713043406554198631721553391137627950991272221023311*z6) * y 347 | c.muls += 7 348 | Dy = ((( x + 349 | 5432652610908059517272798285879155923388888734491153551238890455750936314542*z2)*x + 350 | 10408918692925056833786833257634153023990087029210292532869619559576527581706*z4)*x + 351 | 28948022309329048855892746252171976963363056481941560715954676764349967629797*z6) * z3 352 | c.muls += 6 353 | 354 | if VERBOSE: print("num_x = 0x%064x\ndiv_x = 0x%064x\nnum_y = 0x%064x\ndiv_y = 0x%064x" % (Nx, Dx, Ny, Dy)) 355 | 356 | zo = c.mul(Dx, Dy) 357 | xo = c.mul(c.mul(Nx, Dy), zo) 358 | yo = c.mul(c.mul(Ny, Dx), c.sqr(zo)) 359 | 360 | assert isop_map_affine(x / z2, y / z3, Cost()) == (xo / zo^2, yo / zo^3) 361 | return (xo, yo, zo) 362 | 363 | 364 | # iso_Eq = Isogeny of degree 3 from Elliptic Curve defined by y^2 = x^3 + 17413348858408915339762682399132325137863850198379221683097628341577494210225*x + 1265 over Fp 365 | def isoq_map_affine(x, y, c): 366 | c.muls += 2+1+1 + 2+1+1+2 367 | # batch inversion 368 | c.muls += 3 369 | c.invs += 1 370 | 371 | Nx = (((25731575386070265649682441113041757300767161317281464337493104665238544842753 *x + 372 | 13377367003779316331268047403600734872799183885837485433911493934102207511749)*x + 373 | 11064082577423419940183149293632076317553812518550871517841037420579891210813)*x + 374 | 22515128462811482443472135973911537638171266152621281295306466582083726737451) 375 | Dx = (( x + 376 | 4604213796697651557841441623718706001740429044770779386484474413346415813353)*x + 377 | 9250006497141849826017568406346290940322373181457057184910582871723433210981) 378 | 379 | Ny = ((( 8577191795356755216560813704347252433589053772427154779164368221746181614251 *x + 380 | 21162694656554182593580396827886355918081120183889566406795618341247785229923)*x + 381 | 11620280474556824258112134491145636201000922752744881519070727793732904824884)*x + 382 | 13937936667454727226911322269564285204582212380194126516142098360337545123123) * y 383 | Dy = ((( x + 384 | 21380331849711001764708535561664047484292171808126992769566582994216305194078)*x + 385 | 27750019491425549478052705219038872820967119544371171554731748615170299632943)*x + 386 | 28948022309329048855892746252171976963363056481941647379679742748393362947557) 387 | 388 | return (Nx / Dx, Ny / Dy) 389 | 390 | # The same isogeny iso_Eq but with input in Chudnovsky coordinates (Jacobian with z^2 and z^3) 391 | # and output in Jacobian , 392 | # according to "Avoiding inversions" in [WB2019, section 4.3]. 393 | def isoq_map_jacobian(P, c): 394 | (x, y, z, z2, z3) = (P.x, P.y, P.z, P.z2, P.z3) 395 | z4 = c.sqr(z2) 396 | z6 = c.sqr(z3) 397 | 398 | Nx = (((25731575386070265649682441113041757300767161317281464337493104665238544842753 *x + 399 | 13377367003779316331268047403600734872799183885837485433911493934102207511749*z2)*x + 400 | 11064082577423419940183149293632076317553812518550871517841037420579891210813*z4)*x + 401 | 22515128462811482443472135973911537638171266152621281295306466582083726737451*z6) 402 | c.muls += 6 403 | Dx = (( z2 *x + 404 | 4604213796697651557841441623718706001740429044770779386484474413346415813353*z4)*x + 405 | 9250006497141849826017568406346290940322373181457057184910582871723433210981*z6) 406 | c.muls += 4 407 | 408 | Ny = ((( 8577191795356755216560813704347252433589053772427154779164368221746181614251 *x + 409 | 21162694656554182593580396827886355918081120183889566406795618341247785229923*z2)*x + 410 | 11620280474556824258112134491145636201000922752744881519070727793732904824884*z4)*x + 411 | 13937936667454727226911322269564285204582212380194126516142098360337545123123*z6) * y 412 | c.muls += 7 413 | Dy = ((( x + 414 | 21380331849711001764708535561664047484292171808126992769566582994216305194078*z2)*x + 415 | 27750019491425549478052705219038872820967119544371171554731748615170299632943*z4)*x + 416 | 28948022309329048855892746252171976963363056481941647379679742748393362947557*z6) * z3 417 | c.muls += 6 418 | 419 | zo = c.mul(Dx, Dy) 420 | xo = c.mul(c.mul(Nx, Dy), zo) 421 | yo = c.mul(c.mul(Ny, Dx), c.sqr(zo)) 422 | 423 | assert isoq_map_affine(x / z2, y / z3, Cost()) == (xo / zo^2, yo / zo^3) 424 | return (xo, yo, zo) 425 | 426 | def hex_bytes(bs): 427 | return "[%s]" % (", ".join(["%02x" % (as_byte(b),) for b in bs]),) 428 | 429 | def hash(hasher, msg): 430 | if VERBOSE: print(hex_bytes(msg)) 431 | h = hasher() 432 | h.update(msg) 433 | return h.digest() 434 | 435 | SHA256 = (sha256, 32, 64) 436 | BLAKE2b = (blake2b, 64, 128) 437 | 438 | def hash_to_field(modulus, message, DST, count): 439 | outlen = int(count * CHUNKLEN) 440 | uniform_bytes = expand_message_xmd(BLAKE2b, message, DST, outlen) 441 | if VERBOSE: 442 | print("uniform_bytes:") 443 | print(hex_bytes(uniform_bytes[: 64])) 444 | print(hex_bytes(uniform_bytes[64 :])) 445 | 446 | return [Mod(OS2IP(uniform_bytes[CHUNKLEN*i : CHUNKLEN*(i+1)]), modulus) for i in range(count)] 447 | 448 | def OS2IP(bs): 449 | acc = 0 450 | for b in bs: 451 | acc = (acc<<8) + as_byte(b) 452 | return acc 453 | 454 | def expand_message_xmd(H, msg, DST, len_in_bytes): 455 | assert isinstance(DST, bytes) 456 | assert isinstance(msg, bytes) 457 | 458 | (hasher, b_in_bytes, r_in_bytes) = H 459 | assert len(DST) <= 255 460 | ell = (len_in_bytes + b_in_bytes - 1)//b_in_bytes 461 | assert ell <= 255 462 | 463 | DST_prime = DST + as_bytes([len(DST)]) 464 | msg_prime = b"\x00"*r_in_bytes + msg + as_bytes([len_in_bytes >> 8, len_in_bytes & 0xFF, 0]) + DST_prime 465 | 466 | if VERBOSE: print("b_0:") 467 | b_0 = hash(hasher, msg_prime) 468 | if VERBOSE: print("b_1:") 469 | b = hash(hasher, b_0 + b"\x01" + DST_prime) 470 | for i in range(2, ell+1): 471 | if VERBOSE: print("b_%d:" % (i,)) 472 | b += hash(hasher, as_bytes(as_byte(x) ^^ as_byte(y) for x, y in zip(b_0, b[-64 :])) + as_bytes([i]) + DST_prime) 473 | 474 | return b[: len_in_bytes] 475 | 476 | 477 | def hash_to_pallas_jacobian(msg, DST): 478 | c = Cost() 479 | us = hash_to_field(p, msg, DST, 2) 480 | if VERBOSE: print("us = [0x%064x, 0x064%x]" % (us[0], us[1])) 481 | Q0 = map_to_curve_simple_swu(F_p, IsoEp, IsoEpZ, us[0], c) 482 | Q1 = map_to_curve_simple_swu(F_p, IsoEp, IsoEpZ, us[1], c) 483 | 484 | R = Q0.add(Q1, IsoEp, c) 485 | # Q0.add(Q0, IsoEp, Cost()) # check that unified addition works 486 | 487 | # no cofactor clearing needed since Pallas is prime-order 488 | (Px, Py, Pz) = isop_map_jacobian(R, c) 489 | P = Ep((Px / Pz^2, Py / Pz^3)) 490 | return (P, (Px, Py, Pz), c) 491 | 492 | def hash_to_vesta_jacobian(msg, DST): 493 | c = Cost() 494 | us = hash_to_field(q, msg, DST, 2) 495 | if VERBOSE: print("us = [0x%064x, 0x064%x]" % (us[0], us[1])) 496 | Q0 = map_to_curve_simple_swu(F_q, IsoEq, IsoEqZ, us[0], c) 497 | Q1 = map_to_curve_simple_swu(F_q, IsoEq, IsoEqZ, us[1], c) 498 | 499 | R = Q0.add(Q1, IsoEq, c) 500 | # Q0.add(Q0, IsoEq, Cost()) # check that unified addition works 501 | 502 | # no cofactor clearing needed since Vesta is prime-order 503 | (Px, Py, Pz) = isoq_map_jacobian(R, c) 504 | P = Eq((Px / Pz^2, Py / Pz^3)) 505 | return (P, (Px, Py, Pz), c) 506 | 507 | 508 | print("") 509 | P0 = map_to_curve_simple_swu(F_p, IsoEp, IsoEpZ, Mod(0, p), Cost()) 510 | print("Fp: map_to_curve_simple_swu(0) = %r" % (P0.to_affine(IsoEp),)) 511 | P1 = map_to_curve_simple_swu(F_p, IsoEp, IsoEpZ, Mod(1, p), Cost()) 512 | print("Fp: map_to_curve_simple_swu(1) = %r" % (P1.to_affine(IsoEp),)) 513 | Pa = map_to_curve_simple_swu(F_p, IsoEp, IsoEpZ, Mod(0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef0123, p), Cost()) 514 | print("Fp: map_to_curve_simple_swu(0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef0123) = %r" % (Pa.to_affine(IsoEp),)) 515 | print("") 516 | 517 | Q0 = map_to_curve_simple_swu(F_q, IsoEq, IsoEqZ, Mod(0, q), Cost()) 518 | print("Fq: map_to_curve_simple_swu(0) = %r" % (Q0.to_affine(IsoEq),)) 519 | Q1 = map_to_curve_simple_swu(F_q, IsoEq, IsoEqZ, Mod(1, q), Cost()) 520 | print("Fq: map_to_curve_simple_swu(1) = %r" % (Q1.to_affine(IsoEq),)) 521 | Qa = map_to_curve_simple_swu(F_q, IsoEq, IsoEqZ, Mod(0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef0123, q), Cost()) 522 | print("Fq: map_to_curve_simple_swu(0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef0123) = %r" % (Qa.to_affine(IsoEq),)) 523 | print("") 524 | 525 | xyz = isop_map_jacobian( 526 | ChudnovskyPoint(IsoEp, 527 | Mod(0x0a881e4d556945aa9c6cfc47bce1aba6593c053e5e2337adc37f111df5c4419e, p), 528 | Mod(0x035e5c8a06d5cfb4a62eec46f662cb4e6979f7f2b0acf188f234e04434502b47, p), 529 | Mod(0x3af37975b09331256ac4e343558dcbf3575baa717958ef1f11ab791d4fb6f6b4, p)), 530 | Cost()) 531 | print("Ep { x: 0x%064x, y: 0x%064x, z: 0x%064x }" % xyz) 532 | print("") 533 | 534 | # This test vector is chosen so that the first map_to_curve_simple_swu takes the gx1 square 535 | # "branch" and the second takes the gx1 non-square "branch" (opposite to the Vesta test vector). 536 | (P, xyz, c) = hash_to_pallas_jacobian(b"Trans rights now!", b"z.cash:test-pallas_XMD:BLAKE2b_SSWU_RO_") 537 | print("Ep { x: 0x%064x, y: 0x%064x, z: 0x%064x }" % xyz) 538 | print("") 539 | 540 | # This test vector is chosen so that the first map_to_curve_simple_swu takes the gx1 non-square 541 | # "branch" and the second takes the gx1 square "branch" (opposite to the Pallas test vector). 542 | (P, xyz, c) = hash_to_vesta_jacobian(b"hello", b"z.cash:test-vesta_XMD:BLAKE2b_SSWU_RO_") 543 | print("Eq { x: 0x%064x, y: 0x%064x, z: 0x%064x }" % xyz) 544 | print("") 545 | 546 | if OP_COUNT: 547 | iters = 100 548 | for i in range(iters): 549 | (P, xyz, cost) = hash_to_pallas_jacobian(pack(">I", i), b"z.cash:test-pallas_XMD:BLAKE2b_SSWU_RO_") 550 | print(xyz, cost) 551 | -------------------------------------------------------------------------------- /injectivitylemma.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # This checks the cases k = 1 and k = 2 needed to complete the proof of the 4 | # injectivity lemma in Appendix C of . 5 | 6 | from itertools import product 7 | from collections import namedtuple 8 | 9 | cd_pair = namedtuple('cd_pair', ['c', 'd']) 10 | 11 | def c_d(): 12 | yield cd_pair(-1, 0) 13 | yield cd_pair( 1, 0) 14 | yield cd_pair( 0, -1) 15 | yield cd_pair( 0, 1) 16 | 17 | def sums_mod4(cd): 18 | return ((2**(k+1) + sum([cd[j].c * (2**j) for j in range(k)])) % 4, 19 | (2**(k+1) + sum([cd[j].d * (2**j) for j in range(k)])) % 4) 20 | 21 | for k in (1, 2): 22 | M_k = [list(s) for s in product(c_d(), repeat=k)] 23 | assert(len(M_k) == 4**k) 24 | 25 | for cd in M_k: 26 | print("%r -> %r" % (cd, sums_mod4(cd))) 27 | 28 | for (cd, cd_dash) in product(M_k, repeat=2): 29 | if cd[0] != cd_dash[0]: 30 | assert(sums_mod4(cd) != sums_mod4(cd_dash)) 31 | 32 | print("QED") 33 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sage verify.sage Ep 3 | sage verify.sage Eq 4 | 5 | echo "" 6 | echo "Ep (Pallas)" 7 | echo "-----------" 8 | grep -Rn '.' Ep/verify-* |grep '^Ep/verify-.*:1:' |sed 's/:1:/ = /' 9 | echo "" 10 | echo "Eq (Vesta)" 11 | echo "----------" 12 | grep -Rn '.' Eq/verify-* |grep '^Eq/verify-.*:1:' |sed 's/:1:/ = /' 13 | 14 | -------------------------------------------------------------------------------- /sinsemilla.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | 3 | load('hashtocurve.sage') 4 | 5 | VERBOSE = False 6 | 7 | k = 10 8 | c = len(format((q-1)//2, 'b'))-1 9 | assert c == 253 10 | 11 | def grouphash(D, M): 12 | (P, _, _) = hash_to_pallas_jacobian(M, D + b"-pallas_XMD:BLAKE2b_SSWU_RO_") 13 | return P 14 | 15 | S = [grouphash(b"z.cash:SinsemillaS", pack(', for the Pasta fields. 5 | 6 | import sys 7 | from copy import copy 8 | from collections import deque 9 | 10 | if sys.version_info[0] == 2: 11 | range = xrange 12 | 13 | DEBUG = True 14 | VERBOSE = False 15 | EXPENSIVE = False 16 | 17 | def count_bits(x): 18 | return len(format(x, 'b')) 19 | 20 | def count_ones(x): 21 | return sum([int(b) for b in format(x, 'b')]) 22 | 23 | 24 | class Cost: 25 | def __init__(self, sqrs, muls): 26 | self.sqrs = sqrs 27 | self.muls = muls 28 | 29 | def __repr__(self): 30 | return repr((self.sqrs, self.muls)) 31 | 32 | def __add__(self, other): 33 | return Cost(self.sqrs + other.sqrs, self.muls + other.muls) 34 | 35 | def divide(self, divisor): 36 | return Cost((self.sqrs / divisor).numerical_approx(), (self.muls / divisor).numerical_approx()) 37 | 38 | 39 | class SqrtField: 40 | def __init__(self, p, z, base_cost): 41 | n = 32 42 | m = p >> n 43 | assert p == 1 + m * 2^n 44 | if EXPENSIVE: assert Mod(z, p).multiplicative_order() == p-1 45 | g = Mod(z, p)^m 46 | if EXPENSIVE: assert g.multiplicative_order() == 2^n 47 | 48 | gtab = [[0]*256 for i in range(4)] 49 | gi = g 50 | for i in range(4): 51 | if DEBUG: assert gi == g^(256^i), (i, gi) 52 | acc = Mod(1, p) 53 | for j in range(256): 54 | if DEBUG: assert acc == g^(256^i * j), (i, j, acc) 55 | gtab[i][j] = acc 56 | acc *= gi 57 | gi = acc 58 | 59 | minus1 = Mod(-1, p) 60 | 61 | (self.p, self.n, self.m, self.g, self.gtab, self.minus1, self.base_cost) = ( 62 | p, n, m, g, gtab, minus1, base_cost) 63 | 64 | if DEBUG: 65 | for k in range(32): 66 | self.g_to_power_of_2(k) 67 | 68 | def g_to_power_of_2(self, k): 69 | res = self.gtab[k // 8][1<<(k % 8)] 70 | if DEBUG: 71 | expected = self.g^(2^k) 72 | assert res == expected, (k, self.g, res, expected) 73 | return res 74 | 75 | def mul_by_g_to(self, acc, t, cost): 76 | if VERBOSE: print(t, count_bits(t), count_ones(t)) 77 | if DEBUG: expected = acc * self.g^t 78 | 79 | for i in range(4): 80 | acc *= self.gtab[i][t % 256] 81 | t >>= 8 82 | cost.muls += 1 83 | 84 | if DEBUG: assert acc == expected, (t, acc, expected) 85 | return acc 86 | 87 | def eval(self, alpha, cost): 88 | if EXPENSIVE: 89 | order = alpha.multiplicative_order() 90 | assert order.divides(2^self.n) 91 | if VERBOSE: print("order = 0b%s" % (format(order, 'b'),)) 92 | 93 | delta = alpha 94 | s = 0 95 | if DEBUG: assert delta == alpha * self.g^s 96 | if DEBUG: bits = deque() 97 | 98 | while delta != 1: 99 | # find(delta) 100 | mu = delta 101 | i = 0 102 | while mu != self.minus1: 103 | mu *= mu 104 | cost.sqrs += 1 105 | i += 1 106 | assert i < self.n 107 | # end find 108 | 109 | k = self.n-1-i 110 | if DEBUG: 111 | assert k >= 23 112 | assert k not in bits 113 | bits.append(k) 114 | if VERBOSE: print(bits) 115 | s += 1< 0: 117 | delta *= self.g_to_power_of_2(k) 118 | if DEBUG: assert delta == alpha * self.g^s 119 | cost.muls += 1 120 | else: 121 | delta = -delta 122 | if DEBUG: assert delta == alpha * self.g^s 123 | 124 | if DEBUG: assert 1 == alpha * self.g^s 125 | return s 126 | 127 | def sarkar_sqrt(self, u): 128 | if VERBOSE: print("u = %r" % (u,)) 129 | 130 | # This would actually be done using the addition chain. 131 | v = u^((self.m-1)/2) 132 | cost = copy(self.base_cost) 133 | 134 | uv = u * v 135 | x = uv * v 136 | cost.muls += 2 137 | if DEBUG: assert x == u^self.m 138 | if EXPENSIVE: assert x.multiplicative_order().divides(2^self.n) 139 | 140 | x3 = x 141 | x2 = x3^(1<<8) 142 | x1 = x2^(1<<8) 143 | x0 = x1^(1<<8) 144 | if DEBUG: 145 | assert x0 == x^(1<<(self.n-1-7)) 146 | assert x1 == x^(1<<(self.n-1-15)) 147 | assert x2 == x^(1<<(self.n-1-23)) 148 | assert x3 == x^(1<<(self.n-1-31)) 149 | 150 | cost.sqrs += 8+8+8 151 | 152 | # i = 0 153 | s = self.eval(x0, cost) 154 | 155 | # i = 1 156 | t = s >> 8 157 | alpha = self.mul_by_g_to(x1, t, cost) 158 | s = self.eval(alpha, cost) 159 | 160 | # i = 2 161 | t = (s+t) >> 8 162 | alpha = self.mul_by_g_to(x2, t, cost) 163 | s = self.eval(alpha, cost) 164 | 165 | # i = 3 166 | t = (s+t) >> 8 167 | alpha = self.mul_by_g_to(x3, t, cost) 168 | s = self.eval(alpha, cost) 169 | 170 | t = (s+t) >> 1 171 | res = self.mul_by_g_to(uv, t, cost) 172 | 173 | if res^2 != u: 174 | res = None 175 | cost.sqrs += 1 176 | if DEBUG: assert u.is_square() == (res is not None) 177 | return (res, cost) 178 | 179 | 180 | p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 181 | q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 182 | 183 | # see addchain.py for base costs of u^{(m-1)/2} 184 | F_p = SqrtField(p, 5, Cost(223, 23)) 185 | F_q = SqrtField(q, 5, Cost(223, 24)) 186 | 187 | 188 | print("p = %r" % (p,)) 189 | 190 | x = Mod(0x1234567890123456789012345678901234567890123456789012345678901234, p) 191 | print(F_p.sarkar_sqrt(x)) 192 | 193 | x = Mod(0x2345678901234567890123456789012345678901234567890123456789012345, p) 194 | print(F_p.sarkar_sqrt(x)) 195 | 196 | # nonsquare 197 | x = Mod(0x3456789012345678901234567890123456789012345678901234567890123456, p) 198 | print(F_p.sarkar_sqrt(x)) 199 | 200 | if True: 201 | total_cost = Cost(0, 0) 202 | iters = 1000 203 | for i in range(iters): 204 | x = GF(p).random_element() 205 | (_, cost) = F_p.sarkar_sqrt(x) 206 | total_cost += cost 207 | 208 | print total_cost.divide(iters) 209 | -------------------------------------------------------------------------------- /squareroottab.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | 3 | # This implements a prototype of Palash Sarkar's square root algorithm 4 | # from , for the Pasta fields. 5 | 6 | import sys 7 | 8 | if sys.version_info[0] == 2: 9 | range = xrange 10 | 11 | DEBUG = True 12 | VERBOSE = False 13 | EXPENSIVE = False 14 | 15 | SUBGROUP_TEST = False 16 | OP_COUNT = False 17 | 18 | 19 | class Cost: 20 | def __init__(self, sqrs=0, muls=0, invs=0): 21 | self.sqrs = sqrs 22 | self.muls = muls 23 | self.invs = invs 24 | 25 | def sqr(self, x): 26 | self.sqrs += 1 27 | return x^2 28 | 29 | def mul(self, x, y): 30 | self.muls += 1 31 | return x * y 32 | 33 | def div(self, x, y): 34 | self.invs += 1 35 | self.muls += 1 36 | return x / y 37 | 38 | def batch_inv0(self, xs): 39 | self.invs += 1 40 | self.muls += 3*(len(xs)-1) 41 | # This should use Montgomery's trick (with constant-time substitutions to handle zeros). 42 | return [0 if x == 0 else x^-1 for x in xs] 43 | 44 | def __repr__(self): 45 | return "%dS + %dM + %dI" % (self.sqrs, self.muls, self.invs) 46 | 47 | def __add__(self, other): 48 | return Cost(self.sqrs + other.sqrs, self.muls + other.muls, self.invs + other.invs) 49 | 50 | def include(self, other): 51 | self.sqrs += other.sqrs 52 | self.muls += other.muls 53 | 54 | def divide(self, divisor): 55 | return Cost((self.sqrs / divisor).numerical_approx(), (self.muls / divisor).numerical_approx()) 56 | 57 | 58 | class SqrtField: 59 | def __init__(self, p, z, base_cost, hash_xor=None, hash_mod=None): 60 | n = 32 61 | m = p >> n 62 | assert p == 1 + m * 2^n 63 | if EXPENSIVE: assert Mod(z, p).multiplicative_order() == p-1 64 | g = Mod(z, p)^m 65 | if EXPENSIVE: assert g.multiplicative_order() == 2^n 66 | 67 | gtab = [[0]*256 for i in range(4)] 68 | gi = g 69 | for i in range(4): 70 | if DEBUG: assert gi == g^(256^i), (i, gi) 71 | acc = Mod(1, p) 72 | for j in range(256): 73 | if DEBUG: assert acc == g^(256^i * j), (i, j, acc) 74 | gtab[i][j] = acc 75 | acc *= gi 76 | gi = acc 77 | 78 | if hash_xor is None: 79 | (hash_xor, hash_mod) = self.find_perfect_hash(gtab[3]) 80 | (self.hash_xor, self.hash_mod) = (hash_xor, hash_mod) 81 | 82 | # Now invert gtab[3]. 83 | invtab = [1]*hash_mod 84 | for j in range(256): 85 | h = self.hash(gtab[3][j]) 86 | # 1 is the last value to be assigned, so this ensures there are no collisions. 87 | assert invtab[h] == 1 88 | invtab[h] = (256-j) % 256 89 | 90 | gtab[3] = gtab[3][:129] 91 | 92 | (self.p, self.n, self.m, self.g, self.gtab, self.invtab, self.base_cost) = ( 93 | p, n, m, g, gtab, invtab, base_cost) 94 | 95 | def hash(self, x): 96 | return ((int(x) & 0xFFFFFFFF) ^^ self.hash_xor) % self.hash_mod 97 | 98 | def find_perfect_hash(self, gt): 99 | gt = [int(x) & 0xFFFFFFFF for x in gt] 100 | assert len(set(gt)) == len(gt) 101 | 102 | def is_ok(c_invtab, c_xor, c_mod): 103 | for j in range(256): 104 | hash = (gt[j] ^^ c_xor) % c_mod 105 | if c_invtab[hash] == c_mod: 106 | return False 107 | c_invtab[hash] = c_mod 108 | 109 | return True 110 | 111 | hash_xor = None 112 | hash_mod = 10000 113 | for c_xor in range(1, 0x200000): 114 | c_invtab = [0]*hash_mod 115 | for c_mod in range(256, hash_mod): 116 | if is_ok(c_invtab, c_xor, c_mod): 117 | (hash_xor, hash_mod) = (c_xor, c_mod) 118 | print("0x%X: %d" % (hash_xor, hash_mod)) 119 | break 120 | 121 | print("best is hash_xor=0x%X, hash_mod=%d" % (hash_xor, hash_mod)) 122 | return (hash_xor, hash_mod) 123 | 124 | """ 125 | Return (sqrt(u), True ), if u is square in the field. 126 | (sqrt(g*u), False), otherwise. 127 | """ 128 | def sarkar_sqrt(self, u, c): 129 | if VERBOSE: print("u = %r" % (u,)) 130 | 131 | # This would actually be done using the addition chain. 132 | v = u^((self.m-1)/2) 133 | c.include(self.base_cost) 134 | 135 | uv = c.mul(u, v) 136 | (res, zero_if_square) = self.sarkar_sqrt_common(u, 1, uv, v, c) 137 | return (res, zero_if_square) 138 | 139 | """ 140 | Return (sqrt(N/D), True ), if N/D is square in the field. 141 | (sqrt(g*N/D), False), otherwise. 142 | 143 | This avoids the full cost of computing N/D. 144 | """ 145 | def sarkar_divsqrt(self, N, D, c): 146 | if DEBUG: 147 | u = N/D 148 | if VERBOSE: print("N/D = %r/%r\n = %r" % (N, D, u)) 149 | 150 | # We need to calculate uv and v, where v = u^((m-1)/2), u = N/D, and p-1 = m * 2^n. 151 | # We can rewrite as follows: 152 | # 153 | # v = (N/D)^((m-1)/2) 154 | # = N^((m-1)/2) * D^(p-1 - (m-1)/2) [Fermat's Little Theorem] 155 | # = " * D^(m * 2^n - (m-1)/2) 156 | # = " * D^((2^(n+1) - 1)*(m-1)/2 + 2^n) 157 | # = (N * D^(2^(n+1) - 1))^((m-1)/2) * D^(2^n) 158 | # 159 | # Let w = (N * D^(2^(n+1) - 1))^((m-1)/2) * D^(2^n - 1). 160 | # Then v = w * D, and uv = N * v/D = N * w. 161 | # 162 | # We calculate: 163 | # 164 | # s = D^(2^n - 1) using an addition chain 165 | # t = D^(2^(n+1) - 1) = s^2 * D 166 | # w = (N * t)^((m-1)/2) * s using another addition chain 167 | # 168 | # then u and uv as above. The addition chains are given in addchain_sqrt.py . 169 | # The overall cost of this part is similar to a single full-width exponentiation, 170 | # regardless of n. 171 | 172 | s = D^(2^self.n - 1) 173 | c.sqrs += 31 174 | c.muls += 5 175 | t = c.mul(c.sqr(s), D) 176 | if DEBUG: assert t == D^(2^(self.n+1) - 1) 177 | w = c.mul(c.mul(N, t)^((self.m-1)/2), s) 178 | c.include(self.base_cost) 179 | v = c.mul(w, D) 180 | uv = c.mul(N, w) 181 | 182 | if DEBUG: 183 | assert v == u^((self.m-1)/2) 184 | assert uv == u * v 185 | 186 | (res, zero_if_square) = self.sarkar_sqrt_common(N, D, uv, v, c) 187 | 188 | if DEBUG: 189 | (res_ref, zero_if_square_ref) = self.sarkar_sqrt(u, Cost()) 190 | assert res == res_ref 191 | assert (zero_if_square == 0) == (zero_if_square_ref == 0) 192 | 193 | return (res, zero_if_square) 194 | 195 | def sarkar_sqrt_common(self, N, D, uv, v, c): 196 | x3 = uv * v 197 | c.muls += 2 198 | if DEBUG: 199 | u = N/D 200 | assert x3 == u^self.m 201 | if EXPENSIVE: 202 | x3_order = x3.multiplicative_order() 203 | if VERBOSE: print("x3_order = %r" % (x3_order,)) 204 | # x3_order is 2^n iff u is nonsquare, otherwise it divides 2^(n-1). 205 | assert x3.divides(2^self.n) 206 | 207 | x2 = x3^(1<<8) 208 | x1 = x2^(1<<8) 209 | x0 = x1^(1<<8) 210 | if DEBUG: 211 | assert x0 == x3^(1<<(self.n-1-7)) 212 | assert x1 == x3^(1<<(self.n-1-15)) 213 | assert x2 == x3^(1<<(self.n-1-23)) 214 | 215 | c.sqrs += 8+8+8 216 | 217 | # i = 0, 1 218 | t_ = self.invtab[self.hash(x0)] # = t >> 16 219 | if DEBUG: assert 1 == x0 * self.g^(t_ << 24), (x0, t_) 220 | assert t_ < 0x100, t_ 221 | alpha = x1 * self.gtab[2][t_] 222 | c.muls += 1 223 | 224 | # i = 2 225 | t_ += self.invtab[self.hash(alpha)] << 8 # = t >> 8 226 | if DEBUG: assert 1 == x1 * self.g^(t_ << 16), (x1, t_) 227 | assert t_ < 0x10000, t_ 228 | alpha = x2 * self.gtab[1][t_ % 256] * self.gtab[2][t_ >> 8] 229 | c.muls += 2 230 | 231 | # i = 3 232 | t_ += self.invtab[self.hash(alpha)] << 16 # = t 233 | if DEBUG: assert 1 == x2 * self.g^(t_ << 8), (x2, t_) 234 | assert t_ < 0x1000000, t_ 235 | alpha = x3 * self.gtab[0][t_ % 256] * self.gtab[1][(t_ >> 8) % 256] * self.gtab[2][t_ >> 16] 236 | c.muls += 3 237 | 238 | t_ += self.invtab[self.hash(alpha)] << 24 # = t << 1 239 | if DEBUG: assert 1 == x3 * self.g^t_, (x3, t_) 240 | t_ = (t_ + 1) >> 1 241 | assert t_ <= 0x80000000, t_ 242 | res = uv * self.gtab[0][t_ % 256] * self.gtab[1][(t_ >> 8) % 256] * self.gtab[2][(t_ >> 16) % 256] * self.gtab[3][t_ >> 24] 243 | c.muls += 4 244 | 245 | zero_if_square = c.mul(c.sqr(res), D) - N 246 | if DEBUG: 247 | assert (zero_if_square == 0) == u.is_square() 248 | if EXPENSIVE: assert (zero_if_square == 0) == (x3_order != 2^self.n), (zero_if_square, x3_order) 249 | if zero_if_square != 0: 250 | assert(res^2 == u * self.g) 251 | 252 | return (res, zero_if_square) 253 | 254 | 255 | p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 256 | q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 257 | 258 | # see addchain_sqrt.py for base costs of u^{(m-1)/2} 259 | F_p = SqrtField(p, 5, Cost(223, 23), hash_xor=0x11BE, hash_mod=1098) 260 | F_q = SqrtField(q, 5, Cost(223, 24), hash_xor=0x116A9E, hash_mod=1206) 261 | 262 | print("p = %r" % (p,)) 263 | 264 | x = Mod(0x1234567890123456789012345678901234567890123456789012345678901234, p) 265 | print(F_p.sarkar_sqrt(x, Cost())) 266 | Dx = Mod(0x123456, p) 267 | print(F_p.sarkar_divsqrt(x*Dx, Dx, Cost())) 268 | 269 | x = Mod(0x2345678901234567890123456789012345678901234567890123456789012345, p) 270 | print(F_p.sarkar_sqrt(x, Cost())) 271 | 272 | # nonsquare 273 | x = Mod(0x3456789012345678901234567890123456789012345678901234567890123456, p) 274 | print(F_p.sarkar_sqrt(x, Cost())) 275 | 276 | if SUBGROUP_TEST: 277 | for i in range(33): 278 | x = F_p.g^(2^i) 279 | print(F_p.sarkar_sqrt(x, Cost())) 280 | 281 | if OP_COUNT: 282 | cost = Cost() 283 | iters = 50 284 | for i in range(iters): 285 | x = GF(p).random_element() 286 | y = GF(p).random_element() 287 | (_, _) = F_p.sarkar_divsqrt(x, y, cost) 288 | 289 | print(cost.divide(iters)) 290 | -------------------------------------------------------------------------------- /squareroottab16.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | 3 | # This implements a prototype of Palash Sarkar's square root algorithm 4 | # from , for the Pasta fields. 5 | 6 | import sys 7 | from copy import copy 8 | 9 | if sys.version_info[0] == 2: 10 | range = xrange 11 | 12 | DEBUG = True 13 | VERBOSE = False 14 | EXPENSIVE = False 15 | 16 | SUBGROUP_TEST = True 17 | OP_COUNT = True 18 | 19 | class Cost: 20 | def __init__(self, sqrs, muls): 21 | self.sqrs = sqrs 22 | self.muls = muls 23 | 24 | def __repr__(self): 25 | return repr((self.sqrs, self.muls)) 26 | 27 | def __add__(self, other): 28 | return Cost(self.sqrs + other.sqrs, self.muls + other.muls) 29 | 30 | def divide(self, divisor): 31 | return Cost((self.sqrs / divisor).numerical_approx(), (self.muls / divisor).numerical_approx()) 32 | 33 | 34 | class SqrtField: 35 | def __init__(self, p, z, base_cost, hash_xor=None, hash_mod=None): 36 | n = 32 37 | m = p >> n 38 | assert p == 1 + m * 2^n 39 | if EXPENSIVE: assert Mod(z, p).multiplicative_order() == p-1 40 | g = Mod(z, p)^m 41 | if EXPENSIVE: assert g.multiplicative_order() == 2^n 42 | 43 | gtab = [[0]*16 for i in range(8)] 44 | gi = g 45 | for i in range(8): 46 | if DEBUG: assert gi == g^(16^i), (i, gi) 47 | acc = Mod(1, p) 48 | for j in range(16): 49 | if DEBUG: assert acc == g^(16^i * j), (i, j, acc) 50 | gtab[i][j] = acc 51 | acc *= gi 52 | gi = acc 53 | 54 | if hash_xor is None: 55 | (hash_xor, hash_mod) = self.find_perfect_hash(gtab[7]) 56 | (self.hash_xor, self.hash_mod) = (hash_xor, hash_mod) 57 | 58 | # Now invert gtab[7]. 59 | invtab = [1]*hash_mod 60 | for j in range(16): 61 | h = self.hash(gtab[7][j]) 62 | # 1 is the last value to be assigned, so this ensures there are no collisions. 63 | assert invtab[h] == 1 64 | invtab[h] = (16-j) % 16 65 | 66 | gtab[7] = gtab[7][:8] 67 | 68 | (self.p, self.n, self.m, self.g, self.gtab, self.invtab, self.base_cost) = ( 69 | p, n, m, g, gtab, invtab, base_cost) 70 | 71 | def hash(self, x): 72 | return ((int(x) & 0xFFFFFFFF) ^^ self.hash_xor) % self.hash_mod 73 | 74 | def find_perfect_hash(self, gt): 75 | gt = [int(x) & 0xFFFFFFFF for x in gt] 76 | assert len(set(gt)) == len(gt) 77 | 78 | def is_ok(c_invtab, c_xor, c_mod): 79 | for j in range(16): 80 | hash = (gt[j] ^^ c_xor) % c_mod 81 | if c_invtab[hash] == c_mod: 82 | return False 83 | c_invtab[hash] = c_mod 84 | 85 | return True 86 | 87 | hash_xor = None 88 | hash_mod = 10000 89 | for c_xor in range(0, 0x200000): 90 | c_invtab = [0]*hash_mod 91 | for c_mod in range(16, hash_mod): 92 | if is_ok(c_invtab, c_xor, c_mod): 93 | (hash_xor, hash_mod) = (c_xor, c_mod) 94 | print("0x%X: %d" % (hash_xor, hash_mod)) 95 | break 96 | 97 | print("best is hash_xor=0x%X, hash_mod=%d" % (hash_xor, hash_mod)) 98 | return (hash_xor, hash_mod) 99 | 100 | def sarkar_sqrt(self, u): 101 | if VERBOSE: print("u = %r" % (u,)) 102 | 103 | # This would actually be done using the addition chain. 104 | v = u^((self.m-1)/2) 105 | cost = copy(self.base_cost) 106 | 107 | uv = u * v 108 | x7 = uv * v 109 | cost.muls += 2 110 | if DEBUG: assert x7 == u^self.m 111 | if EXPENSIVE: 112 | x7_order = x7.multiplicative_order() 113 | if VERBOSE: print("x7_order = %r" % (x7_order,)) 114 | # x7_order is 2^n iff u is nonsquare, otherwise it divides 2^(n-1). 115 | assert x7.divides(2^self.n) 116 | 117 | x6 = x7^(1<<4) 118 | x5 = x6^(1<<4) 119 | x4 = x5^(1<<4) 120 | x3 = x4^(1<<4) 121 | x2 = x3^(1<<4) 122 | x1 = x2^(1<<4) 123 | x0 = x1^(1<<4) 124 | if DEBUG: 125 | assert x0 == x7^(1<<(self.n-1-3)) 126 | assert x1 == x7^(1<<(self.n-1-7)) 127 | assert x2 == x7^(1<<(self.n-1-11)) 128 | assert x3 == x7^(1<<(self.n-1-15)) 129 | assert x4 == x7^(1<<(self.n-1-19)) 130 | assert x5 == x7^(1<<(self.n-1-23)) 131 | assert x6 == x7^(1<<(self.n-1-27)) 132 | 133 | cost.sqrs += 4*7 134 | 135 | # i = 0, 1 136 | t_ = self.invtab[self.hash(x0)] # = t >> 24 137 | if DEBUG: assert 1 == x0 * self.g^(t_ << 28), (x0, t_) 138 | assert t_ < 0x10, t_ 139 | alpha = x1 * self.gtab[6][t_] 140 | cost.muls += 1 141 | 142 | # i = 2 143 | t_ += self.invtab[self.hash(alpha)] << 4 # = t >> 20 144 | if DEBUG: assert 1 == x1 * self.g^(t_ << 24), (x1, t_) 145 | assert t_ < 0x100, t_ 146 | alpha = x2 * self.gtab[5][t_ % 16] * self.gtab[6][t_ >> 4] 147 | cost.muls += 2 148 | 149 | # i = 3 150 | t_ += self.invtab[self.hash(alpha)] << 8 # = t >> 16 151 | if DEBUG: assert 1 == x2 * self.g^(t_ << 20), (x2, t_) 152 | assert t_ < 0x1000, t_ 153 | alpha = x3 * self.gtab[4][t_ % 16] * self.gtab[5][(t_ >> 4) % 16] * self.gtab[6][t_ >> 8] 154 | cost.muls += 2 155 | 156 | # i = 4 157 | t_ += self.invtab[self.hash(alpha)] << 12 # = t >> 12 158 | if DEBUG: assert 1 == x3 * self.g^(t_ << 16), (x3, t_) 159 | assert t_ < 0x10000, t_ 160 | alpha = x4 * self.gtab[3][t_ % 16] * self.gtab[4][(t_ >> 4) % 16] * self.gtab[5][(t_ >> 8) % 16] * self.gtab[6][t_ >> 12] 161 | cost.muls += 4 162 | 163 | # i = 5 164 | t_ += self.invtab[self.hash(alpha)] << 16 # = t >> 8 165 | if DEBUG: assert 1 == x4 * self.g^(t_ << 12), (x4, t_) 166 | assert t_ < 0x100000, t_ 167 | alpha = x5 * self.gtab[2][t_ % 16] * self.gtab[3][(t_ >> 4) % 16] * self.gtab[4][(t_ >> 8) % 16] * self.gtab[5][(t_ >> 12) % 16] * self.gtab[6][t_ >> 16] 168 | cost.muls += 5 169 | 170 | # i = 6 171 | t_ += self.invtab[self.hash(alpha)] << 20 # = t >> 4 172 | if DEBUG: assert 1 == x5 * self.g^(t_ << 8), (x5, t_) 173 | assert t_ < 0x1000000, t_ 174 | alpha = x6 * self.gtab[1][t_ % 16] * self.gtab[2][(t_ >> 4) % 16] * self.gtab[3][(t_ >> 8) % 16] * self.gtab[4][(t_ >> 12) % 16] * self.gtab[5][(t_ >> 16) % 16] * self.gtab[6][t_ >> 20] 175 | cost.muls += 6 176 | 177 | # i = 7 178 | t_ += self.invtab[self.hash(alpha)] << 24 # = t 179 | if DEBUG: assert 1 == x6 * self.g^(t_ << 4), (x6, t_) 180 | assert t_ < 0x10000000, t_ 181 | alpha = x7 * self.gtab[0][t_ % 16] * self.gtab[1][(t_ >> 4) % 16] * self.gtab[2][(t_ >> 8) % 16] * self.gtab[3][(t_ >> 12) % 16] * self.gtab[4][(t_ >> 16) % 16] * self.gtab[5][(t_ >> 20) % 16] * self.gtab[6][t_ >> 24] 182 | cost.muls += 7 183 | 184 | t_ += self.invtab[self.hash(alpha)] << 28 # = t << 1 185 | if DEBUG: assert 1 == x7 * self.g^t_, (x7, t_) 186 | t_ >>= 1 187 | assert t_ < 0x80000000, t_ 188 | res = uv * self.gtab[0][t_ % 16] * self.gtab[1][(t_ >> 4) % 16] * self.gtab[2][(t_ >> 8) % 16] * self.gtab[3][(t_ >> 12) % 16] * self.gtab[4][(t_ >> 16) % 16] * self.gtab[5][(t_ >> 20) % 16] * self.gtab[6][(t_ >> 24) % 16] * self.gtab[7][t_ >> 28] 189 | cost.muls += 8 190 | 191 | if res^2 != u: 192 | res = None 193 | cost.sqrs += 1 194 | if DEBUG: 195 | issq = u.is_square() 196 | assert issq == (res is not None) 197 | if EXPENSIVE: assert issq == (x7_order != 2^self.n), (issq, x7_order) 198 | return (res, cost) 199 | 200 | 201 | p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 202 | q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 203 | 204 | # see addchain_sqrt.py for base costs of u^{(m-1)/2} 205 | F_p = SqrtField(p, 5, Cost(223, 23), hash_xor=0xC847, hash_mod=17) 206 | F_q = SqrtField(q, 5, Cost(223, 24), hash_xor=0xFA68, hash_mod=17) 207 | 208 | 209 | print("p = %r" % (p,)) 210 | 211 | x = Mod(0x1234567890123456789012345678901234567890123456789012345678901234, p) 212 | print(F_p.sarkar_sqrt(x)) 213 | 214 | x = Mod(0x2345678901234567890123456789012345678901234567890123456789012345, p) 215 | print(F_p.sarkar_sqrt(x)) 216 | 217 | # nonsquare 218 | x = Mod(0x3456789012345678901234567890123456789012345678901234567890123456, p) 219 | print(F_p.sarkar_sqrt(x)) 220 | 221 | if SUBGROUP_TEST: 222 | for i in range(33): 223 | x = F_p.g^(2^i) 224 | print(F_p.sarkar_sqrt(x)) 225 | 226 | if OP_COUNT: 227 | total_cost = Cost(0, 0) 228 | iters = 50 229 | for i in range(iters): 230 | x = GF(p).random_element() 231 | (_, cost) = F_p.sarkar_sqrt(x) 232 | total_cost += cost 233 | 234 | print total_cost.divide(iters) 235 | -------------------------------------------------------------------------------- /subgroupcheck.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | 3 | # Find the smallest element > 1 of { \omega^j : j \in [0, 2^32) }, over the Pasta Fp and Fq. 4 | # 5 | # This is a bit clunky at the moment since the threads work independently on subsets 6 | # of the space, so it requires you to scan the output by eye to get the actual smallest 7 | # element for each field. 8 | 9 | import sys 10 | from multiprocessing import Pool, cpu_count 11 | from traceback import print_exc 12 | 13 | if sys.version_info[0] == 2: 14 | range = xrange 15 | 16 | 17 | p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 18 | q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 19 | 20 | def check(ps): 21 | workers = cpu_count()//len(ps) 22 | pool = Pool(processes=workers*len(ps)) 23 | 24 | try: 25 | for (which, p) in ps.items(): 26 | print("Checking %s = %r" % (which, p)) 27 | t = p >> 32 28 | omega = GF(p).multiplicative_generator()^t 29 | assert omega.multiplicative_order() == 1<<32 30 | 31 | for wid in range(1, workers+1): 32 | pool.apply_async(worker, (which, p, omega, wid, workers)) 33 | 34 | while True: 35 | sleep(1000) 36 | except (KeyboardInterrupt, SystemExit): 37 | pass 38 | finally: 39 | pool.terminate() 40 | 41 | 42 | def worker(*args): 43 | try: 44 | real_worker(*args) 45 | except (KeyboardInterrupt, SystemExit): 46 | pass 47 | except: 48 | print_exc() 49 | 50 | def real_worker(which, p, omega, wid, workers): 51 | print("Worker %d for %s" % (wid, which)) 52 | 53 | lowest = 1<<240 54 | dot = 0 55 | 56 | x = omega^wid 57 | m = omega^workers 58 | 59 | for i in range(wid, 1<<32, workers): 60 | if dot == 65536: 61 | sys.stdout.write('.') 62 | sys.stdout.flush() 63 | dot = 0 64 | dot += 1 65 | 66 | if int(x) < lowest: 67 | lowest = int(x) 68 | print("\n%s: i = %r, %r (%d bits)" % (which, i, lowest, len(format(lowest, 'b')))) 69 | 70 | x *= m 71 | 72 | return lowest 73 | 74 | 75 | check({"p": p, "q": q}) 76 | -------------------------------------------------------------------------------- /verify.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | 3 | import os 4 | import sys 5 | import traceback 6 | from errno import ENOENT, EEXIST 7 | 8 | 9 | def readfile(fn): 10 | fd = open(fn,'r') 11 | r = fd.read() 12 | fd.close() 13 | return r 14 | 15 | def writefile(fn,s): 16 | fd = open(fn,'w') 17 | fd.write(s) 18 | fd.close() 19 | 20 | def expand2(n): 21 | s = "" 22 | 23 | while n != 0: 24 | j = 16 25 | while 2**j < abs(n): j += 1 26 | if 2**j - abs(n) > abs(n) - 2**(j-1): j -= 1 27 | 28 | if abs(abs(n) - 2**j) > 2**(j - 10): 29 | if n > 0: 30 | if s != "": s += " + " 31 | s += str(n) 32 | else: 33 | s += " - " + str(-n) 34 | n = 0 35 | elif n > 0: 36 | if s != "": s += " + " 37 | s += "2^" + str(j) 38 | n -= 2**j 39 | else: 40 | s += " - 2^" + str(j) 41 | n += 2**j 42 | 43 | return s 44 | 45 | def requirement(fn,istrue): 46 | writefile(fn,str(istrue) + '\n') 47 | return istrue 48 | 49 | def verify(): 50 | try: 51 | os.mkdir('proof') 52 | except OSError as e: 53 | if e.errno != EEXIST: raise 54 | 55 | try: 56 | s = set(map(Integer, readfile('primes').split())) 57 | except IOError as e: 58 | if e.errno != ENOENT: raise 59 | s = set() 60 | 61 | needtofactor = set() 62 | V = set() # distinct verified primes 63 | verify_primes(V, s, needtofactor) 64 | verify_pass(V, needtofactor) 65 | 66 | old = V 67 | needtofactor.update(V) 68 | while len(needtofactor) > len(old): 69 | k = len(needtofactor) - len(old) 70 | sys.stdout.write('Factoring %d integer%s' % (k, '' if k == 1 else 's')) 71 | sys.stdout.flush() 72 | for x in sorted(needtofactor): 73 | if x not in old: 74 | for (y, z) in factor(x): 75 | s.add(y) 76 | sys.stdout.write('.') 77 | sys.stdout.flush() 78 | 79 | print('') 80 | 81 | old = needtofactor.copy() 82 | verify_primes(V, s, needtofactor) 83 | 84 | writefile('primes', '\n'.join(map(str, s)) + '\n') 85 | writefile('verify-primes', '\n' + 86 | ''.join(('2\n' if v == 2 else 87 | '%s\n' % (v,v)) for v in V) + 88 | '\n') 89 | 90 | verify_pass(V, needtofactor) 91 | 92 | 93 | def verify_primes(V, s, needtofactor): 94 | for n in sorted(s): 95 | if not n.is_prime() or n in V: continue 96 | needtofactor.add(n-1) 97 | if n == 2: 98 | V.add(n) 99 | continue 100 | for trybase in primes(2,10000): 101 | base = Integers(n)(trybase) 102 | if base^(n-1) != 1: continue 103 | proof = 'Primality proof for n = %s:\n' % n 104 | proof += '

Take b = %s.\n' % base 105 | proof += '

b^(n-1) mod n = 1.\n' 106 | f = factor(1) 107 | for v in sorted(V, reverse=True): 108 | if f.prod()^2 <= n: 109 | if n % v == 1: 110 | u = base^((n-1)/v)-1 111 | if u.is_unit(): 112 | if v == 2: 113 | proof += '

2 is prime.\n' 114 | else: 115 | proof += '

%s is prime.\n' % (v,v) 116 | proof += '
b^((n-1)/%s)-1 mod n = %s, which is a unit, inverse %s.\n' % (v,u,1/u) 117 | f *= factor(v)^(n-1).valuation(v) 118 | if f.prod()^2 <= n: continue 119 | if n % f.prod() != 1: continue 120 | proof += '

(%s) divides n-1.\n' % f 121 | proof += '

(%s)^2 > n.\n' % f 122 | proof += "

n is prime by Pocklington's theorem.\n" 123 | proof += '\n' 124 | writefile('proof/%s.html' % n,proof) 125 | V.add(n) 126 | break 127 | 128 | 129 | def verify_pass(V, needtofactor): 130 | V = sorted(V) 131 | p = Integer(readfile('p')) 132 | k = GF(p) 133 | kz. = k[] 134 | l = Integer(readfile('l')) 135 | x0 = Integer(readfile('x0')) 136 | y0 = Integer(readfile('y0')) 137 | x1 = Integer(readfile('x1')) 138 | y1 = Integer(readfile('y1')) 139 | shape = readfile('shape').strip() 140 | rigid = readfile('rigid').strip() 141 | 142 | safefield = True 143 | safeeq = True 144 | safebase = True 145 | saferho = True 146 | safetransfer = True 147 | safedisc = True 148 | saferigid = True 149 | safeladder = True 150 | safetwist = True 151 | safecomplete = True 152 | safeind = True 153 | 154 | pstatus = 'Unverified' 155 | if not p.is_prime(): pstatus = 'False' 156 | needtofactor.add(p) 157 | if p in V: pstatus = 'True' 158 | if pstatus != 'True': safefield = False 159 | writefile('verify-pisprime',pstatus + '\n') 160 | 161 | pstatus = 'Unverified' 162 | if not l.is_prime(): pstatus = 'False' 163 | needtofactor.add(l) 164 | if l in V: pstatus = 'True' 165 | if pstatus != 'True': safebase = False 166 | writefile('verify-lisprime',pstatus + '\n') 167 | 168 | writefile('expand2-p','= %s\n' % expand2(p)) 169 | writefile('expand2-l','
= %s\n' % expand2(l)) 170 | 171 | writefile('hex-p',p.hex() + '\n') 172 | writefile('hex-l',l.hex() + '\n') 173 | writefile('hex-x0',x0.hex() + '\n') 174 | writefile('hex-x1',x1.hex() + '\n') 175 | writefile('hex-y0',y0.hex() + '\n') 176 | writefile('hex-y1',y1.hex() + '\n') 177 | 178 | gcdlpis1 = gcd(l,p) == 1 179 | safetransfer &= requirement('verify-gcdlp1',gcdlpis1) 180 | 181 | writefile('verify-movsafe','Unverified\n') 182 | writefile('verify-embeddingdegree','Unverified\n') 183 | if gcdlpis1 and l.is_prime(): 184 | u = Integers(l)(p) 185 | d = l-1 186 | needtofactor.add(d) 187 | for v in V: 188 | while d % v == 0: d /= v 189 | if d == 1: 190 | d = l-1 191 | for v in V: 192 | while d % v == 0: 193 | if u^(d/v) != 1: break 194 | d /= v 195 | safetransfer &= requirement('verify-movsafe',(l-1)/d <= 100) 196 | writefile('verify-embeddingdegree','%s
= (l-1)/%s\n' % (d,(l-1)/d)) 197 | 198 | t = p+1-l*round((p+1)/l) 199 | if l^2 > 16*p: 200 | writefile('verify-trace','%s\n' % t) 201 | f = factor(1) 202 | d = (p+1-t)/l 203 | needtofactor.add(d) 204 | for v in V: 205 | while d % v == 0: 206 | d //= v 207 | f *= factor(v) 208 | writefile('verify-cofactor','%s\n' % f) 209 | else: 210 | writefile('verify-trace','Unverified\n') 211 | writefile('verify-cofactor','Unverified\n') 212 | 213 | D = t^2-4*p 214 | needtofactor.add(D) 215 | for v in V: 216 | while D % v^2 == 0: D /= v^2 217 | if prod([v for v in V if D % v == 0]) != -D: 218 | writefile('verify-disc','Unverified\n') 219 | writefile('verify-discisbig','Unverified\n') 220 | safedisc = False 221 | else: 222 | f = -prod([factor(v) for v in V if D % v == 0]) 223 | if D % 4 != 1: 224 | D *= 4 225 | f = factor(4) * f 226 | Dbits = (log(-D)/log(2)).numerical_approx() 227 | writefile('verify-disc','%s
= %s
≈ -2^%.1f\n' % (D,f,Dbits)) 228 | safedisc &= requirement('verify-discisbig',D < -2^100) 229 | 230 | pin = 0.78539816339744830961566084581987572105 231 | if D == -3: 232 | pin /= 3.0 233 | rho = log(pin*l)/log(4) 234 | writefile('verify-rho','%.1f\n' % rho) 235 | saferho &= requirement('verify-rhoabove100',rho.numerical_approx() >= 100) 236 | 237 | twistl = 'Unverified' 238 | d = p+1+t 239 | needtofactor.add(d) 240 | for v in V: 241 | while d % v == 0: d /= v 242 | if d == 1: 243 | d = p+1+t 244 | for v in V: 245 | if d % v == 0: 246 | if twistl == 'Unverified' or v > twistl: twistl = v 247 | 248 | writefile('verify-twistl','%s\n' % twistl) 249 | writefile('verify-twistembeddingdegree','Unverified\n') 250 | writefile('verify-twistmovsafe','Unverified\n') 251 | if twistl == 'Unverified': 252 | writefile('hex-twistl','Unverified\n') 253 | writefile('expand2-twistl','Unverified\n') 254 | writefile('verify-twistcofactor','Unverified\n') 255 | writefile('verify-gcdtwistlp1','Unverified\n') 256 | writefile('verify-twistrho','Unverified\n') 257 | safetwist = False 258 | else: 259 | writefile('hex-twistl',twistl.hex() + '\n') 260 | writefile('expand2-twistl','
= %s\n' % expand2(twistl)) 261 | f = factor(1) 262 | d = (p+1+t)/twistl 263 | needtofactor.add(d) 264 | for v in V: 265 | while d % v == 0: 266 | d //= v 267 | f *= factor(v) 268 | writefile('verify-twistcofactor','%s\n' % f) 269 | gcdtwistlpis1 = gcd(twistl,p) == 1 270 | safetwist &= requirement('verify-gcdtwistlp1',gcdtwistlpis1) 271 | 272 | movsafe = 'Unverified' 273 | embeddingdegree = 'Unverified' 274 | if gcdtwistlpis1 and twistl.is_prime(): 275 | u = Integers(twistl)(p) 276 | d = twistl-1 277 | needtofactor.add(d) 278 | for v in V: 279 | while d % v == 0: d /= v 280 | if d == 1: 281 | d = twistl-1 282 | for v in V: 283 | while d % v == 0: 284 | if u^(d/v) != 1: break 285 | d /= v 286 | safetwist &= requirement('verify-twistmovsafe',(twistl-1)/d <= 100) 287 | writefile('verify-twistembeddingdegree',"%s
= (l'-1)/%s\n" % (d,(twistl-1)/d)) 288 | 289 | rho = log(pin*twistl)/log(4) 290 | writefile('verify-twistrho','%.1f\n' % rho) 291 | safetwist &= requirement('verify-twistrhoabove100',rho.numerical_approx() >= 100) 292 | 293 | precomp = 0 294 | joint = l 295 | needtofactor.add(p+1-t) 296 | needtofactor.add(p+1+t) 297 | for v in V: 298 | d1 = p+1-t 299 | d2 = p+1+t 300 | while d1 % v == 0 or d2 % v == 0: 301 | if d1 % v == 0: d1 //= v 302 | if d2 % v == 0: d2 //= v 303 | # best case for attack: cyclic; each power is usable 304 | # also assume that kangaroo is as efficient as rho 305 | if v + sqrt(pin*joint/v) < sqrt(pin*joint): 306 | precomp += v 307 | joint /= v 308 | 309 | rho = log(precomp + sqrt(pin * joint))/log(2) 310 | writefile('verify-jointrho','%.1f\n' % rho) 311 | safetwist &= requirement('verify-jointrhoabove100',rho.numerical_approx() >= 100) 312 | 313 | 314 | x0 = k(x0) 315 | y0 = k(y0) 316 | x1 = k(x1) 317 | y1 = k(y1) 318 | 319 | if shape in ('edwards', 'tedwards'): 320 | d = Integer(readfile('d')) 321 | a = 1 322 | if shape == 'tedwards': 323 | a = Integer(readfile('a')) 324 | 325 | writefile('verify-shape','Twisted Edwards\n') 326 | writefile('verify-equation','%sx^2+y^2 = 1%+dx^2y^2\n' % (a, d)) 327 | if a == 1: 328 | writefile('verify-shape','Edwards\n') 329 | writefile('verify-equation','x^2+y^2 = 1%+dx^2y^2\n' % d) 330 | 331 | a = k(a) 332 | d = k(d) 333 | elliptic = a*d*(a-d) 334 | level0 = a*x0^2+y0^2-1-d*x0^2*y0^2 335 | level1 = a*x1^2+y1^2-1-d*x1^2*y1^2 336 | 337 | if shape == 'montgomery': 338 | writefile('verify-shape','Montgomery\n') 339 | A = Integer(readfile('A')) 340 | B = Integer(readfile('B')) 341 | equation = '%sy^2 = x^3%+dx^2+x' % (B,A) 342 | if B == 1: 343 | equation = 'y^2 = x^3%+dx^2+x' % A 344 | writefile('verify-equation',equation + '\n') 345 | 346 | A = k(A) 347 | B = k(B) 348 | elliptic = B*(A^2-4) 349 | level0 = B*y0^2-x0^3-A*x0^2-x0 350 | level1 = B*y1^2-x1^3-A*x1^2-x1 351 | 352 | if shape == 'shortw': 353 | writefile('verify-shape','short Weierstrass\n') 354 | a = Integer(readfile('a')) 355 | b = Integer(readfile('b')) 356 | writefile('verify-equation','y^2 = x^3%+dx%+d\n' % (a,b)) 357 | 358 | a = k(a) 359 | b = k(b) 360 | elliptic = 4*a^3+27*b^2 361 | level0 = y0^2-x0^3-a*x0-b 362 | level1 = y1^2-x1^3-a*x1-b 363 | 364 | writefile('verify-elliptic',str(elliptic) + '\n') 365 | safeeq &= requirement('verify-iselliptic',elliptic != 0) 366 | safebase &= requirement('verify-isoncurve0',level0 == 0) 367 | safebase &= requirement('verify-isoncurve1',level1 == 0) 368 | 369 | if shape in ('edwards', 'tedwards'): 370 | A = 2*(a+d)/(a-d) 371 | B = 4/(a-d) 372 | x0,y0 = (1+y0)/(1-y0),((1+y0)/(1-y0))/x0 373 | x1,y1 = (1+y1)/(1-y1),((1+y1)/(1-y1))/x1 374 | shape = 'montgomery' 375 | 376 | if shape == 'montgomery': 377 | a = (3-A^2)/(3*B^2) 378 | b = (2*A^3-9*A)/(27*B^3) 379 | x0,y0 = (x0+A/3)/B,y0/B 380 | x1,y1 = (x1+A/3)/B,y1/B 381 | shape = 'shortw' 382 | 383 | try: 384 | E = EllipticCurve([a,b]) 385 | numorder2 = 0 386 | numorder4 = 0 387 | for P in E(0).division_points(4): 388 | if P != 0 and 2*P == 0: 389 | numorder2 += 1 390 | if 2*P != 0 and 4*P == 0: 391 | numorder4 += 1 392 | writefile('verify-numorder2',str(numorder2) + '\n') 393 | writefile('verify-numorder4',str(numorder4) + '\n') 394 | completesingle = False 395 | completemulti = False 396 | if numorder4 == 2 and numorder2 == 1: 397 | # complete edwards form, and montgomery with unique point of order 2 398 | completesingle = True 399 | completemulti = True 400 | # should extend this to allow complete twisted hessian 401 | safecomplete &= requirement('verify-completesingle',completesingle) 402 | safecomplete &= requirement('verify-completemulti',completemulti) 403 | safecomplete &= requirement('verify-ltimesbase1is0',l * E([x1,y1]) == 0) 404 | writefile('verify-ltimesbase1',str(l * E([x1,y1])) + '\n') 405 | writefile('verify-cofactorbase01',str(((p+1-t)//l) * E([x0,y0]) == E([x1,y1])) + '\n') 406 | except: 407 | traceback.print_exc() 408 | writefile('verify-numorder2','Unverified\n') 409 | writefile('verify-numorder4','Unverified\n') 410 | writefile('verify-ltimesbase1','Unverified\n') 411 | writefile('verify-cofactorbase01','Unverified\n') 412 | safecomplete = False 413 | 414 | montladder = False 415 | for r,e in (z^3+a*z+b).roots(): 416 | if (3*r^2+a).is_square(): 417 | montladder = True 418 | safeladder &= requirement('verify-montladder',montladder) 419 | 420 | indistinguishability = False 421 | elligator2 = False 422 | if (p+1-t) % 2 == 0: 423 | if b != 0: 424 | indistinguishability = True 425 | elligator2 = True 426 | safeind &= requirement('verify-indistinguishability',indistinguishability) 427 | writefile('verify-ind-notes','Elligator 2: %s.\n' % ['No','Yes'][elligator2]) 428 | 429 | saferigid &= (rigid == 'fully rigid' or rigid == 'somewhat rigid') 430 | 431 | safecurve = True 432 | safecurve &= requirement('verify-safefield',safefield) 433 | safecurve &= requirement('verify-safeeq',safeeq) 434 | safecurve &= requirement('verify-safebase',safebase) 435 | safecurve &= requirement('verify-saferho',saferho) 436 | safecurve &= requirement('verify-safetransfer',safetransfer) 437 | safecurve &= requirement('verify-safedisc',safedisc) 438 | safecurve &= requirement('verify-saferigid',saferigid) 439 | safecurve &= requirement('verify-safeladder',safeladder) 440 | safecurve &= requirement('verify-safetwist',safetwist) 441 | safecurve &= requirement('verify-safecomplete',safecomplete) 442 | safecurve &= requirement('verify-safeind',safeind) 443 | requirement('verify-safecurve',safecurve) 444 | 445 | originaldir = os.open('.',os.O_RDONLY) 446 | for i in range(1,len(sys.argv)): 447 | os.fchdir(originaldir) 448 | os.chdir(sys.argv[i]) 449 | verify() 450 | 451 | --------------------------------------------------------------------------------