diff --git a/doc/evidence/.gitignore b/doc/evidence/.gitignore new file mode 100644 index 0000000..9a0d287 --- /dev/null +++ b/doc/evidence/.gitignore @@ -0,0 +1,102 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + diff --git a/doc/evidence/LICENSE b/doc/evidence/LICENSE new file mode 100644 index 0000000..9e18163 --- /dev/null +++ b/doc/evidence/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 The Zcash developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/doc/evidence/README.md b/doc/evidence/README.md new file mode 100644 index 0000000..26b2e36 --- /dev/null +++ b/doc/evidence/README.md @@ -0,0 +1,28 @@ +Jubjub supporting evidence +-------------------------- + +This repository contains supporting evidence that the twisted Edwards curve +-x^2 + y^2 = 1 - (10240/10241).x^2.y^2 of rational points over +GF(52435875175126190479447740508185965837690552500527637822603658699938581184513), +[also called "Jubjub"](https://z.cash/technology/jubjub.html), +satisfies the [SafeCurves criteria](https://safecurves.cr.yp.to/index.html). + +The script ``verify.sage`` is based on +[this script from the SafeCurves site](https://safecurves.cr.yp.to/verify.html), +modified + +* to support twisted Edwards curves; +* to generate a file 'primes' containing the primes needed for primality proofs, + if it is not already present; +* to change the directory in which Pocklington proof files are generated + (``proof/`` rather than ``../../../proof``), and to create that directory + if it does not exist. + +Prerequisites: + +* apt-get install sagemath +* pip install sortedcontainers + +Run ``sage verify.sage .``, or ``./run.sh`` to also print out the results. + +Note that the "rigidity" criterion cannot be checked automatically. diff --git a/doc/evidence/a b/doc/evidence/a new file mode 100644 index 0000000..3a2e3f4 --- /dev/null +++ b/doc/evidence/a @@ -0,0 +1 @@ +-1 diff --git a/doc/evidence/d b/doc/evidence/d new file mode 100644 index 0000000..767309a --- /dev/null +++ b/doc/evidence/d @@ -0,0 +1 @@ +19257038036680949359750312669786877991949435402254120286184196891950884077233 diff --git a/doc/evidence/l b/doc/evidence/l new file mode 100644 index 0000000..83f92d5 --- /dev/null +++ b/doc/evidence/l @@ -0,0 +1 @@ +6554484396890773809930967563523245729705921265872317281365359162392183254199 diff --git a/doc/evidence/p b/doc/evidence/p new file mode 100644 index 0000000..1dc0557 --- /dev/null +++ b/doc/evidence/p @@ -0,0 +1 @@ +52435875175126190479447740508185965837690552500527637822603658699938581184513 diff --git a/doc/evidence/rigid b/doc/evidence/rigid new file mode 100644 index 0000000..e560e40 --- /dev/null +++ b/doc/evidence/rigid @@ -0,0 +1 @@ +fully rigid diff --git a/doc/evidence/run.sh b/doc/evidence/run.sh new file mode 100644 index 0000000..817f2fa --- /dev/null +++ b/doc/evidence/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh +sage verify.sage . +grep -Rn '.' verify-* |grep '^verify-.*:1:' |sed 's/:1:/ = /' + diff --git a/doc/evidence/shape b/doc/evidence/shape new file mode 100644 index 0000000..796f74d --- /dev/null +++ b/doc/evidence/shape @@ -0,0 +1 @@ +tedwards diff --git a/doc/evidence/verify.sage b/doc/evidence/verify.sage new file mode 100644 index 0000000..1717c0b --- /dev/null +++ b/doc/evidence/verify.sage @@ -0,0 +1,444 @@ +import os +import sys +from errno import ENOENT, EEXIST +from sortedcontainers import SortedSet + + +def readfile(fn): + fd = open(fn,'r') + r = fd.read() + fd.close() + return r + +def writefile(fn,s): + fd = open(fn,'w') + fd.write(s) + fd.close() + +def expand2(n): + s = "" + + while n != 0: + j = 16 + while 2**j < abs(n): j += 1 + if 2**j - abs(n) > abs(n) - 2**(j-1): j -= 1 + + if abs(abs(n) - 2**j) > 2**(j - 10): + if n > 0: + if s != "": s += " + " + s += str(n) + else: + s += " - " + str(-n) + n = 0 + elif n > 0: + if s != "": s += " + " + s += "2^" + str(j) + n -= 2**j + else: + s += " - 2^" + str(j) + n += 2**j + + return s + +def requirement(fn,istrue): + writefile(fn,str(istrue) + '\n') + return istrue + +def verify(): + try: + os.mkdir('proof') + except OSError as e: + if e.errno != EEXIST: raise + + try: + s = set(map(Integer, readfile('primes').split())) + except IOError, e: + if e.errno != ENOENT: raise + s = set() + + needtofactor = SortedSet() + V = SortedSet() # distinct verified primes + verify_primes(V, s, needtofactor) + verify_pass(V, needtofactor) + + old = V + needtofactor.update(V) + while len(needtofactor) > len(old): + k = len(needtofactor) - len(old) + sys.stdout.write('Factoring %d integer%s' % (k, '' if k == 1 else 's')) + sys.stdout.flush() + for x in needtofactor: + if x not in old: + for (y, z) in factor(x): + s.add(y) + sys.stdout.write('.') + sys.stdout.flush() + + print('') + + old = needtofactor.copy() + verify_primes(V, s, needtofactor) + + writefile('primes', '\n'.join(map(str, s)) + '\n') + writefile('verify-primes', '
\n' + + ''.join(('2\n' if v == 2 else + '%s\n' % (v,v)) for v in V) + + '\n') + + verify_pass(V, needtofactor) + + +def verify_primes(V, s, needtofactor): + for n in sorted(s): + if not n.is_prime() or n in V: continue + needtofactor.add(n-1) + if n == 2: + V.add(n) + continue + for trybase in primes(2,10000): + base = Integers(n)(trybase) + if base^(n-1) != 1: continue + proof = 'Primality proof for n = %s:\n' % n + proof += 'Take b = %s.\n' % base + proof += '
b^(n-1) mod n = 1.\n' + f = factor(1) + for v in reversed(V): + if f.prod()^2 <= n: + if n % v == 1: + u = base^((n-1)/v)-1 + if u.is_unit(): + if v == 2: + proof += '
2 is prime.\n' + else: + proof += '
%s is prime.\n' % (v,v)
+ proof += '
b^((n-1)/%s)-1 mod n = %s, which is a unit, inverse %s.\n' % (v,u,1/u)
+ f *= factor(v)^(n-1).valuation(v)
+ if f.prod()^2 <= n: continue
+ if n % f.prod() != 1: continue
+ proof += '
(%s) divides n-1.\n' % f + proof += '
(%s)^2 > n.\n' % f + proof += "
n is prime by Pocklington's theorem.\n"
+ proof += '\n'
+ writefile('proof/%s.html' % n,proof)
+ V.add(n)
+ break
+
+
+def verify_pass(V, needtofactor):
+ p = Integer(readfile('p'))
+ k = GF(p)
+ kz.
= %s\n' % expand2(l))
+
+ writefile('hex-p',hex(p) + '\n')
+ writefile('hex-l',hex(l) + '\n')
+ writefile('hex-x0',hex(x0) + '\n')
+ writefile('hex-x1',hex(x1) + '\n')
+ writefile('hex-y0',hex(y0) + '\n')
+ writefile('hex-y1',hex(y1) + '\n')
+
+ gcdlpis1 = gcd(l,p) == 1
+ safetransfer &= requirement('verify-gcdlp1',gcdlpis1)
+
+ writefile('verify-movsafe','Unverified\n')
+ writefile('verify-embeddingdegree','Unverified\n')
+ if gcdlpis1 and l.is_prime():
+ u = Integers(l)(p)
+ d = l-1
+ needtofactor.add(d)
+ for v in V:
+ while d % v == 0: d /= v
+ if d == 1:
+ d = l-1
+ for v in V:
+ while d % v == 0:
+ if u^(d/v) != 1: break
+ d /= v
+ safetransfer &= requirement('verify-movsafe',(l-1)/d <= 100)
+ writefile('verify-embeddingdegree','%s
= (l-1)/%s\n' % (d,(l-1)/d))
+
+ t = p+1-l*round((p+1)/l)
+ if l^2 > 16*p:
+ writefile('verify-trace','%s\n' % t)
+ f = factor(1)
+ d = (p+1-t)/l
+ needtofactor.add(d)
+ for v in V:
+ while d % v == 0:
+ d //= v
+ f *= factor(v)
+ writefile('verify-cofactor','%s\n' % f)
+ else:
+ writefile('verify-trace','Unverified\n')
+ writefile('verify-cofactor','Unverified\n')
+
+ D = t^2-4*p
+ needtofactor.add(D)
+ for v in V:
+ while D % v^2 == 0: D /= v^2
+ if prod([v for v in V if D % v == 0]) != -D:
+ writefile('verify-disc','Unverified\n')
+ writefile('verify-discisbig','Unverified\n')
+ safedisc = False
+ else:
+ f = -prod([factor(v) for v in V if D % v == 0])
+ if D % 4 != 1:
+ D *= 4
+ f = factor(4) * f
+ Dbits = (log(-D)/log(2)).numerical_approx()
+ writefile('verify-disc','%s
= %s
≈ -2^%.1f\n' % (D,f,Dbits))
+ safedisc &= requirement('verify-discisbig',D < -2^100)
+
+ pi4 = 0.78539816339744830961566084581987572105
+ rho = log(pi4*l)/log(4)
+ writefile('verify-rho','%.1f\n' % rho)
+ saferho &= requirement('verify-rhoabove100',rho.numerical_approx() >= 100)
+
+ twistl = 'Unverified'
+ d = p+1+t
+ needtofactor.add(d)
+ for v in V:
+ while d % v == 0: d /= v
+ if d == 1:
+ d = p+1+t
+ for v in V:
+ if d % v == 0:
+ if twistl == 'Unverified' or v > twistl: twistl = v
+
+ writefile('verify-twistl','%s\n' % twistl)
+ writefile('verify-twistembeddingdegree','Unverified\n')
+ writefile('verify-twistmovsafe','Unverified\n')
+ if twistl == 'Unverified':
+ writefile('hex-twistl','Unverified\n')
+ writefile('expand2-twistl','Unverified\n')
+ writefile('verify-twistcofactor','Unverified\n')
+ writefile('verify-gcdtwistlp1','Unverified\n')
+ writefile('verify-twistrho','Unverified\n')
+ safetwist = False
+ else:
+ writefile('hex-twistl',hex(twistl) + '\n')
+ writefile('expand2-twistl','
= %s\n' % expand2(twistl))
+ f = factor(1)
+ d = (p+1+t)/twistl
+ needtofactor.add(d)
+ for v in V:
+ while d % v == 0:
+ d //= v
+ f *= factor(v)
+ writefile('verify-twistcofactor','%s\n' % f)
+ gcdtwistlpis1 = gcd(twistl,p) == 1
+ safetwist &= requirement('verify-gcdtwistlp1',gcdtwistlpis1)
+
+ movsafe = 'Unverified'
+ embeddingdegree = 'Unverified'
+ if gcdtwistlpis1 and twistl.is_prime():
+ u = Integers(twistl)(p)
+ d = twistl-1
+ needtofactor.add(d)
+ for v in V:
+ while d % v == 0: d /= v
+ if d == 1:
+ d = twistl-1
+ for v in V:
+ while d % v == 0:
+ if u^(d/v) != 1: break
+ d /= v
+ safetwist &= requirement('verify-twistmovsafe',(twistl-1)/d <= 100)
+ writefile('verify-twistembeddingdegree',"%s
= (l'-1)/%s\n" % (d,(twistl-1)/d))
+
+ rho = log(pi4*twistl)/log(4)
+ writefile('verify-twistrho','%.1f\n' % rho)
+ safetwist &= requirement('verify-twistrhoabove100',rho.numerical_approx() >= 100)
+
+ precomp = 0
+ joint = l
+ needtofactor.add(p+1-t)
+ needtofactor.add(p+1+t)
+ for v in V:
+ d1 = p+1-t
+ d2 = p+1+t
+ while d1 % v == 0 or d2 % v == 0:
+ if d1 % v == 0: d1 //= v
+ if d2 % v == 0: d2 //= v
+ # best case for attack: cyclic; each power is usable
+ # also assume that kangaroo is as efficient as rho
+ if v + sqrt(pi4*joint/v) < sqrt(pi4*joint):
+ precomp += v
+ joint /= v
+
+ rho = log(precomp + sqrt(pi4 * joint))/log(2)
+ writefile('verify-jointrho','%.1f\n' % rho)
+ safetwist &= requirement('verify-jointrhoabove100',rho.numerical_approx() >= 100)
+
+
+ x0 = k(x0)
+ y0 = k(y0)
+ x1 = k(x1)
+ y1 = k(y1)
+
+ if shape in ('edwards', 'tedwards'):
+ d = Integer(readfile('d'))
+ a = 1
+ if shape == 'tedwards':
+ a = Integer(readfile('a'))
+
+ writefile('verify-shape','Twisted Edwards\n')
+ writefile('verify-equation','%sx^2+y^2 = 1%+dx^2y^2\n' % (a, d))
+ if a == 1:
+ writefile('verify-shape','Edwards\n')
+ writefile('verify-equation','x^2+y^2 = 1%+dx^2y^2\n' % d)
+
+ a = k(a)
+ d = k(d)
+ elliptic = a*d*(a-d)
+ level0 = a*x0^2+y0^2-1-d*x0^2*y0^2
+ level1 = a*x1^2+y1^2-1-d*x1^2*y1^2
+
+ if shape == 'montgomery':
+ writefile('verify-shape','Montgomery\n')
+ A = Integer(readfile('A'))
+ B = Integer(readfile('B'))
+ equation = '%sy^2 = x^3