├── .gitignore ├── README.md ├── elliptic-curves-finite-fields ├── README.md ├── elliptic-basic.py ├── elliptic-generalized.py ├── elliptic.py ├── find-points.py └── finitefield │ ├── __init__.py │ ├── euclidean.py │ ├── finitefield.py │ ├── modp.py │ ├── numbertype.py │ └── polynomial.py ├── secp256k1_openssl.py └── simple-zk-proofs.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-zk-proofs 2 | 3 | This was first created as part of [ECE/CS 598AM Cryptocurrency Security](http://soc1024.ece.illinois.edu/teaching/ece598am/fall2016/ 4 | ) at University of Illinois at Urbana-Champaign 5 | 6 | This file contains several examples of zero-knowledge proof schemes (specifically, Sigma protocols), implemented in python. 7 | 8 | This code uses Jeremy Kun's beautiful [python library for elliptic curves and finite fields](https://github.com/j2kun/elliptic-curves-finite-fields). Alternatives like `gmpy` would be feasible as well. 9 | 10 | Released under the CRAPL license (TODO: add link to CRAPL license) 11 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/README.md: -------------------------------------------------------------------------------- 1 | elliptic-curves-finite-fields 2 | ============================= 3 | 4 | The combined Python code for the post ["Connecting Elliptic Curves with Finite Fields"](http://jeremykun.com/2014/03/19/connecting-elliptic-curves-with-finite-fields-a-reprise/) 5 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/elliptic-basic.py: -------------------------------------------------------------------------------- 1 | 2 | class EllipticCurve(object): 3 | def __init__(self, a, b): 4 | # assume we're already in the Weierstrass form 5 | self.a = a 6 | self.b = b 7 | 8 | self.discriminant = -16 * (4 * a*a*a + 27 * b * b) 9 | if not self.isSmooth(): 10 | raise Exception("The curve %s is not smooth!" % self) 11 | 12 | 13 | def isSmooth(self): 14 | return self.discriminant != 0 15 | 16 | 17 | def testPoint(self, x, y): 18 | return y*y == x*x*x + self.a * x + self.b 19 | 20 | 21 | def __str__(self): 22 | return 'y^2 = x^3 + %sx + %s' % (self.a, self.b) 23 | 24 | 25 | def __repr__(self): 26 | return str(self) 27 | 28 | 29 | def __eq__(self, other): 30 | return (self.a, self.b) == (other.a, other.b) 31 | 32 | 33 | 34 | class Point(object): 35 | def __init__(self, curve, x, y): 36 | self.curve = curve # the curve containing this point 37 | self.x = x 38 | self.y = y 39 | 40 | if not curve.testPoint(x,y): 41 | raise Exception("The point %s is not on the given curve %s!" % (self, curve)) 42 | 43 | 44 | def __str__(self): 45 | return "(%r, %r)" % (self.x, self.y) 46 | 47 | 48 | def __repr__(self): 49 | return str(self) 50 | 51 | 52 | def __neg__(self): 53 | return Point(self.curve, self.x, -self.y) 54 | 55 | 56 | def __add__(self, Q): 57 | if self.curve != Q.curve: 58 | raise Exception("Can't add points on different curves!") 59 | if isinstance(Q, Ideal): 60 | return self 61 | 62 | x_1, y_1, x_2, y_2 = self.x, self.y, Q.x, Q.y 63 | 64 | if (x_1, y_1) == (x_2, y_2): 65 | if y_1 == 0: 66 | return Ideal(self.curve) 67 | 68 | # slope of the tangent line 69 | m = (3 * x_1 * x_1 + self.curve.a) / (2 * y_1) 70 | else: 71 | if x_1 == x_2: 72 | return Ideal(self.curve) 73 | 74 | # slope of the secant line 75 | m = (y_2 - y_1) / (x_2 - x_1) 76 | 77 | x_3 = m*m - x_2 - x_1 78 | y_3 = m*(x_3 - x_1) + y_1 79 | 80 | return Point(self.curve, x_3, -y_3) 81 | 82 | 83 | def __sub__(self, Q): 84 | return self + -Q 85 | 86 | def __mul__(self, n): 87 | if not isinstance(n, int): 88 | raise Exception("Can't scale a point by something which isn't an int!") 89 | else: 90 | if n < 0: 91 | return -self * -n 92 | if n == 0: 93 | return Ideal(self.curve) 94 | else: 95 | Q = self 96 | R = self if n & 1 == 1 else Ideal(self.curve) 97 | 98 | i = 2 99 | while i <= n: 100 | Q = Q + Q 101 | 102 | if n & i == i: 103 | R = Q + R 104 | 105 | i = i << 1 106 | 107 | return R 108 | 109 | 110 | def __rmul__(self, n): 111 | return self * n 112 | 113 | def __list__(self): 114 | return [self.x, self.y] 115 | 116 | def __eq__(self, other): 117 | if type(other) is Ideal: 118 | return False 119 | 120 | return (self.x, self.y) == (other.x, other.y) 121 | 122 | def __ne__(self, other): 123 | return not self == other 124 | 125 | def __getitem__(self, index): 126 | return [self.x, self.y][index] 127 | 128 | 129 | class Ideal(Point): 130 | def __init__(self, curve): 131 | self.curve = curve 132 | 133 | def __neg__(self): 134 | return self 135 | 136 | def __str__(self): 137 | return "Ideal" 138 | 139 | def __add__(self, Q): 140 | if self.curve != Q.curve: 141 | raise Exception("Can't add points on different curves!") 142 | return Q 143 | 144 | def __mul__(self, n): 145 | if not isinstance(n, int): 146 | raise Exception("Can't scale a point by something which isn't an int!") 147 | else: 148 | return self 149 | 150 | def __eq__(self, other): 151 | return type(other) is Ideal 152 | 153 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/elliptic-generalized.py: -------------------------------------------------------------------------------- 1 | 2 | # An elliptic curve with generalized Weierstrass normal form 3 | class GeneralizedEllipticCurve(object): 4 | def __init__(self, a1=0, a2=0, a3=0, a4=0, a6=0): 5 | #Coefficients are for y^2 + a1xy + a3y = x^3 + a2x^2 + a4x + a6 6 | self.a1 = a1 7 | self.a2 = a2 8 | self.a3 = a3 9 | self.a4 = a4 10 | self.a6 = a6 11 | 12 | b2 = self.a1**2 + 4*self.a2 13 | b4 = 2*self.a4 + self.a1*self.a3 14 | b6 = self.a3**2 + 4*self.a6 15 | b8 = (self.a1**2)*self.a6 + 4*self.a2*self.a6 - self.a1*self.a3*self.a4 + self.a2*(self.a3**2) - self.a4**2 16 | 17 | c4 = b2*b2 - 24*b4 18 | c6 = -b2*b2*b2 + 36*b2*b4 - 216*b6 19 | 20 | self.disc = -b2*b2*b8 - 8*b4*b4*b4 - 27*b6*b6 + 9*b2*b4*b6 21 | self.j = c4*c4*c4/self.disc 22 | 23 | 24 | def testPoint(self, x, y): 25 | return y*y + self.a1*x*y + self.a3*y - x*x*x - self.a2*(x*x) - self.a4*x - self.a6 == 0 26 | 27 | 28 | 29 | class Point(object): 30 | def __init__ (self, curve, x, y): 31 | self.curve = curve # the curve containing this point 32 | self.x = x 33 | self.y = y 34 | 35 | def __str__(self): 36 | return "(%r, %r)" % (self.x, self.y) 37 | 38 | def __repr__(self): 39 | return str(self) 40 | 41 | 42 | def __neg__(self): 43 | return Point(self.curve, self.x, -self.y - self.curve.a1*self.x - self.curve.a3) 44 | 45 | def __add__(self, Q): 46 | if isinstance(Q, Ideal): 47 | return Point(self.curve, self.x, self.y) 48 | 49 | a1,a2,a3,a4,a6 = (self.curve.a1, self.curve.a2, self.curve.a3, self.curve.a4, self.curve.a6) 50 | 51 | if self.x == Q.x: 52 | x = self.x 53 | if self.y + Q.y + a1*x + a3 == 0: 54 | return Ideal(self.curve) 55 | else: 56 | c = ((3*x*x + 2*a2*x + a4 - a1*self.y) / (2*self.y + a1*x + a3)) 57 | d = (-(x*x*x) + a4*x + 2*a6 - a3*self.y) / (2*self.y + a1*x + a3) 58 | Sum_x = c*c + a1*c - a2 - 2*self.x 59 | Sum_y = -(c + a1) * Sum_x - d - a3 60 | return Point(self.curve, Sum_x, Sum_y) 61 | else: 62 | c = (Q.y - self.y) / (Q.x - self.x) 63 | d = (self.y*Q.x - Q.y*self.x) / (Q.x - self.x) 64 | Sum_x = c*c + a1*c - a2 - self.x - Q.x 65 | Sum_y = -(c + a1)*Sum_x - d - a3 66 | return Point(self.curve, Sum_x, Sum_y) 67 | 68 | 69 | def __sub__(self, Q): 70 | return self + -Q 71 | 72 | 73 | def __mul__(self, n): 74 | if not isinstance(n, int): 75 | raise Exception("Can't scale a point by something which isn't an int!") 76 | else: 77 | if n < 0: 78 | return -self * -n 79 | if n == 0: 80 | return Ideal(self.curve) 81 | else: 82 | Q = self 83 | R = self if n & 1 == 1 else Ideal(self.curve) 84 | 85 | i = 2 86 | while i <= n: 87 | Q = Q + Q 88 | 89 | if n & i == i: 90 | R = Q + R 91 | 92 | i = i << 1 93 | 94 | return R 95 | 96 | 97 | def __rmul__(self, n): 98 | return self * n 99 | 100 | def __list__(self): 101 | return [self.x, self.y] 102 | 103 | def __eq__(self, other): 104 | if isinstance(other, Ideal): 105 | return False 106 | return list(self) == list(other) 107 | 108 | def __ne__(self, other): 109 | return not self == other 110 | 111 | def __getitem__(self, index): 112 | return [self.x, self.y][index] 113 | 114 | # lexicographic ordering on points 115 | def __lt__(self, other): 116 | if isinstance(other, Ideal): return False 117 | return list(self) < list(other) 118 | def __gt__(self, other): 119 | return other.__lt__(self) 120 | def __ge__(self, other): 121 | return not self < other 122 | def __le__(self, other): 123 | return not other < self 124 | 125 | 126 | class Ideal(Point): 127 | def __init__(self, curve): 128 | self.curve = curve 129 | 130 | def __neg__(self): 131 | return self 132 | 133 | def __repr__(self): 134 | return "Ideal" 135 | 136 | def __add__(self, Q): 137 | if self.curve != Q.curve: 138 | raise Exception("Can't add points on different curves!") 139 | return Q 140 | 141 | def __mul__(self, n): 142 | if not isinstance(n, int): 143 | raise Exception("Can't scale a point by something which isn't an int!") 144 | else: 145 | return self 146 | 147 | def __eq__(self, other): 148 | return isinstance(other, Ideal) 149 | 150 | def __lt__(self, other): 151 | return not isinstance(other, Ideal) 152 | 153 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/elliptic.py: -------------------------------------------------------------------------------- 1 | 2 | # An elliptic curve with generalized Weierstrass normal form 3 | class GeneralizedEllipticCurve(object): 4 | def __init__(self, a1=0, a2=0, a3=0, a4=0, a6=0): 5 | #Coefficients are for y^2 + a1xy + a3y = x^3 + a2x^2 + a4x + a6 6 | self.a1 = a1 7 | self.a2 = a2 8 | self.a3 = a3 9 | self.a4 = a4 10 | self.a6 = a6 11 | 12 | b2 = self.a1**2 + 4*self.a2 13 | b4 = 2*self.a4 + self.a1*self.a3 14 | b6 = self.a3**2 + 4*self.a6 15 | b8 = (self.a1**2)*self.a6 + 4*self.a2*self.a6 - self.a1*self.a3*self.a4 + self.a2*(self.a3**2) - self.a4**2 16 | 17 | c4 = b2*b2 - 24*b4 18 | c6 = -b2*b2*b2 + 36*b2*b4 - 216*b6 19 | 20 | self.disc = -b2*b2*b8 - 8*b4*b4*b4 - 27*b6*b6 + 9*b2*b4*b6 21 | self.j = c4*c4*c4/self.disc 22 | 23 | 24 | def testPoint(self, x, y): 25 | return y*y + self.a1*x*y + self.a3*y - x*x*x - self.a2*(x*x) - self.a4*x - self.a6 == 0 26 | 27 | 28 | 29 | class Point(object): 30 | def __init__ (self, curve, x, y): 31 | self.curve = curve # the curve containing this point 32 | self.x = x 33 | self.y = y 34 | 35 | def __str__(self): 36 | return "(%r, %r)" % (self.x, self.y) 37 | 38 | def __repr__(self): 39 | return str(self) 40 | 41 | 42 | def __neg__(self): 43 | return Point(self.curve, self.x, -self.y - self.curve.a1*self.x - self.curve.a3) 44 | 45 | def __add__(self, Q): 46 | if isinstance(Q, Ideal): 47 | return Point(self.curve, self.x, self.y) 48 | 49 | a1,a2,a3,a4,a6 = (self.curve.a1, self.curve.a2, self.curve.a3, self.curve.a4, self.curve.a6) 50 | 51 | if self.x == Q.x: 52 | x = self.x 53 | if self.y + Q.y + a1*x + a3 == 0: 54 | return Ideal(self.curve) 55 | else: 56 | c = ((3*x*x + 2*a2*x + a4 - a1*self.y) / (2*self.y + a1*x + a3)) 57 | d = (-(x*x*x) + a4*x + 2*a6 - a3*self.y) / (2*self.y + a1*x + a3) 58 | Sum_x = c*c + a1*c - a2 - 2*self.x 59 | Sum_y = -(c + a1) * Sum_x - d - a3 60 | return Point(self.curve, Sum_x, Sum_y) 61 | else: 62 | c = (Q.y - self.y) / (Q.x - self.x) 63 | d = (self.y*Q.x - Q.y*self.x) / (Q.x - self.x) 64 | Sum_x = c*c + a1*c - a2 - self.x - Q.x 65 | Sum_y = -(c + a1)*Sum_x - d - a3 66 | return Point(self.curve, Sum_x, Sum_y) 67 | 68 | 69 | def __sub__(self, Q): 70 | return self + -Q 71 | 72 | 73 | def __mul__(self, n): 74 | if not isinstance(n, int) and not isinstance(n, long): 75 | raise Exception("Can't scale a point by something which isn't an int!") 76 | else: 77 | if n < 0: 78 | return -self * -n 79 | if n == 0: 80 | return Ideal(self.curve) 81 | else: 82 | Q = self 83 | R = self if n & 1 == 1 else Ideal(self.curve) 84 | 85 | i = 2 86 | while i <= n: 87 | Q = Q + Q 88 | 89 | if n & i == i: 90 | R = Q + R 91 | 92 | i = i << 1 93 | 94 | return R 95 | 96 | 97 | def __rmul__(self, n): 98 | return self * n 99 | 100 | def __list__(self): 101 | return [self.x, self.y] 102 | 103 | def __eq__(self, other): 104 | if isinstance(other, Ideal): 105 | return False 106 | return list(self) == list(other) 107 | 108 | def __ne__(self, other): 109 | return not self == other 110 | 111 | def __getitem__(self, index): 112 | return [self.x, self.y][index] 113 | 114 | # lexicographic ordering on points 115 | def __lt__(self, other): 116 | if isinstance(other, Ideal): return False 117 | return list(self) < list(other) 118 | def __gt__(self, other): 119 | return other.__lt__(self) 120 | def __ge__(self, other): 121 | return not self < other 122 | def __le__(self, other): 123 | return not other < self 124 | 125 | 126 | class Ideal(Point): 127 | def __init__(self, curve): 128 | self.curve = curve 129 | 130 | def __neg__(self): 131 | return self 132 | 133 | def __repr__(self): 134 | return "Ideal" 135 | 136 | def __add__(self, Q): 137 | if self.curve != Q.curve: 138 | raise Exception("Can't add points on different curves!") 139 | return Q 140 | 141 | def __mul__(self, n): 142 | if not isinstance(n, int): 143 | raise Exception("Can't scale a point by something which isn't an int!") 144 | else: 145 | return self 146 | 147 | def __eq__(self, other): 148 | return isinstance(other, Ideal) 149 | 150 | def __lt__(self, other): 151 | return not isinstance(other, Ideal) 152 | 153 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/find-points.py: -------------------------------------------------------------------------------- 1 | from elliptic import * 2 | from fractions import Fraction as frac 3 | 4 | from finitefield.finitefield import FiniteField 5 | 6 | import itertools 7 | 8 | def findPoints(curve, field): 9 | print('Finding all points over %s' % (curve)) 10 | print('The ideal generator is %s' % (field.idealGenerator)) 11 | 12 | degree = field.idealGenerator.degree() 13 | subfield = field.primeSubfield 14 | xs = [field(x) for x in itertools.product(range(subfield.p), repeat=degree)] 15 | ys = [field(x) for x in itertools.product(range(subfield.p), repeat=degree)] 16 | 17 | points = [Point(curve, x, y) for x in xs for y in ys if curve.testPoint(x,y)] 18 | return points 19 | 20 | 21 | 22 | F25 = FiniteField(5, 2) 23 | curve = EllipticCurve(a=F25(1), b=F25(1)) 24 | points = findPoints(curve, F25) 25 | 26 | for point in points: 27 | print(point) 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/finitefield/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiller/python-zk-proofs/b74eac1d7fe105457324e27a1a2968c07b833ce6/elliptic-curves-finite-fields/finitefield/__init__.py -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/finitefield/euclidean.py: -------------------------------------------------------------------------------- 1 | 2 | # a general Euclidean algorithm for any number type with 3 | # a divmod and a valuation abs() whose minimum value is zero 4 | def gcd(a, b): 5 | if abs(a) < abs(b): 6 | return gcd(b, a) 7 | 8 | while abs(b) > 0: 9 | _,r = divmod(a,b) 10 | a,b = b,r 11 | 12 | return a 13 | 14 | 15 | # extendedEuclideanAlgorithm: int, int -> int, int, int 16 | # input (a,b) and output three numbers x,y,d such that ax + by = d = gcd(a,b). 17 | # Works for any number type with a divmod and a valuation abs() 18 | # whose minimum value is zero 19 | def extendedEuclideanAlgorithm(a, b): 20 | if abs(b) > abs(a): 21 | (x,y,d) = extendedEuclideanAlgorithm(b, a) 22 | return (y,x,d) 23 | 24 | if abs(b) == 0: 25 | return (1, 0, a) 26 | 27 | x1, x2, y1, y2 = 0, 1, 1, 0 28 | while abs(b) > 0: 29 | q, r = divmod(a,b) 30 | x = x2 - q*x1 31 | y = y2 - q*y1 32 | a, b, x2, x1, y2, y1 = b, r, x1, x, y1, y 33 | 34 | return (x2, y2, a) 35 | 36 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/finitefield/finitefield.py: -------------------------------------------------------------------------------- 1 | import random 2 | from .polynomial import polynomialsOver 3 | from .modp import * 4 | 5 | 6 | 7 | # isIrreducible: Polynomial, int -> bool 8 | # determine if the given monic polynomial with coefficients in Z/p is 9 | # irreducible over Z/p where p is the given integer 10 | # Algorithm 4.69 in the Handbook of Applied Cryptography 11 | def isIrreducible(polynomial, p): 12 | ZmodP = IntegersModP(p) 13 | if polynomial.field is not ZmodP: 14 | raise TypeError("Given a polynomial that's not over %s, but instead %r" % 15 | (ZmodP.__name__, polynomial.field.__name__)) 16 | 17 | poly = polynomialsOver(ZmodP).factory 18 | x = poly([0,1]) 19 | powerTerm = x 20 | isUnit = lambda p: p.degree() == 0 21 | 22 | for _ in range(int(polynomial.degree() / 2)): 23 | powerTerm = powerTerm.powmod(p, polynomial) 24 | gcdOverZmodp = gcd(polynomial, powerTerm - x) 25 | if not isUnit(gcdOverZmodp): 26 | return False 27 | 28 | return True 29 | 30 | 31 | # generateIrreduciblePolynomial: int, int -> Polynomial 32 | # generate a random irreducible polynomial of a given degree over Z/p, where p 33 | # is given by the integer 'modulus'. This algorithm is expected to terminate 34 | # after 'degree' many irreducilibity tests. By Chernoff bounds the probability 35 | # it deviates from this by very much is exponentially small. 36 | def generateIrreduciblePolynomial(modulus, degree): 37 | Zp = IntegersModP(modulus) 38 | Polynomial = polynomialsOver(Zp) 39 | 40 | while True: 41 | coefficients = [Zp(random.randint(0, modulus-1)) for _ in range(degree)] 42 | randomMonicPolynomial = Polynomial(coefficients + [Zp(1)]) 43 | print(randomMonicPolynomial) 44 | 45 | if isIrreducible(randomMonicPolynomial, modulus): 46 | return randomMonicPolynomial 47 | 48 | 49 | # create a type constructor for the finite field of order p^m for p prime, m >= 1 50 | @memoize 51 | def FiniteField(p, m, polynomialModulus=None): 52 | Zp = IntegersModP(p) 53 | if m == 1: 54 | return Zp 55 | 56 | Polynomial = polynomialsOver(Zp) 57 | if polynomialModulus is None: 58 | polynomialModulus = generateIrreduciblePolynomial(modulus=p, degree=m) 59 | 60 | class Fq(FieldElement): 61 | fieldSize = int(p ** m) 62 | primeSubfield = Zp 63 | idealGenerator = polynomialModulus 64 | operatorPrecedence = 3 65 | 66 | def __init__(self, poly): 67 | if type(poly) is Fq: 68 | self.poly = poly.poly 69 | elif type(poly) is int or type(poly) is Zp: 70 | self.poly = Polynomial([Zp(poly)]) 71 | elif isinstance(poly, Polynomial): 72 | self.poly = poly % polynomialModulus 73 | else: 74 | self.poly = Polynomial([Zp(x) for x in poly]) % polynomialModulus 75 | 76 | self.field = Fq 77 | 78 | @typecheck 79 | def __add__(self, other): return Fq(self.poly + other.poly) 80 | @typecheck 81 | def __sub__(self, other): return Fq(self.poly - other.poly) 82 | @typecheck 83 | def __mul__(self, other): return Fq(self.poly * other.poly) 84 | @typecheck 85 | def __eq__(self, other): return isinstance(other, Fq) and self.poly == other.poly 86 | 87 | def __pow__(self, n): return Fq(pow(self.poly, n)) 88 | def __neg__(self): return Fq(-self.poly) 89 | def __abs__(self): return abs(self.poly) 90 | def __repr__(self): return repr(self.poly) + ' \u2208 ' + self.__class__.__name__ 91 | 92 | @typecheck 93 | def __divmod__(self, divisor): 94 | q,r = divmod(self.poly, divisor.poly) 95 | return (Fq(q), Fq(r)) 96 | 97 | 98 | def inverse(self): 99 | if self == Fq(0): 100 | raise ZeroDivisionError 101 | 102 | x,y,d = extendedEuclideanAlgorithm(self.poly, self.idealGenerator) 103 | if d.degree() != 0: 104 | raise Exception('Somehow, this element has no inverse! Maybe intialized with a non-prime?') 105 | 106 | return Fq(x) * Fq(d.coefficients[0].inverse()) 107 | 108 | 109 | Fq.__name__ = 'F_{%d^%d}' % (p,m) 110 | return Fq 111 | 112 | 113 | if __name__ == "__main__": 114 | F23 = FiniteField(2,3) 115 | x = F23([1,1]) 116 | 117 | F35 = FiniteField(3,5) 118 | y = F35([1,1,2]) 119 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/finitefield/modp.py: -------------------------------------------------------------------------------- 1 | 2 | from .euclidean import * 3 | from .numbertype import * 4 | 5 | # so all IntegersModP are instances of the same base class 6 | class _Modular(FieldElement): 7 | pass 8 | 9 | 10 | @memoize 11 | def IntegersModP(p): 12 | # assume p is prime 13 | 14 | class IntegerModP(_Modular): 15 | def __init__(self, n): 16 | try: 17 | self.n = int(n) % IntegerModP.p 18 | except: 19 | raise TypeError("Can't cast type %s to %s in __init__" % (type(n).__name__, type(self).__name__)) 20 | 21 | self.field = IntegerModP 22 | 23 | @typecheck 24 | def __add__(self, other): 25 | return IntegerModP(self.n + other.n) 26 | 27 | @typecheck 28 | def __sub__(self, other): 29 | return IntegerModP(self.n - other.n) 30 | 31 | @typecheck 32 | def __mul__(self, other): 33 | return IntegerModP(self.n * other.n) 34 | 35 | def __neg__(self): 36 | return IntegerModP(-self.n) 37 | 38 | @typecheck 39 | def __eq__(self, other): 40 | return isinstance(other, IntegerModP) and self.n == other.n 41 | 42 | @typecheck 43 | def __ne__(self, other): 44 | return isinstance(other, IntegerModP) is False or self.n != other.n 45 | 46 | @typecheck 47 | def __divmod__(self, divisor): 48 | q,r = divmod(self.n, divisor.n) 49 | return (IntegerModP(q), IntegerModP(r)) 50 | 51 | def inverse(self): 52 | # need to use the division algorithm *as integers* because we're 53 | # doing it on the modulus itself (which would otherwise be zero) 54 | x,y,d = extendedEuclideanAlgorithm(self.n, self.p) 55 | 56 | if d != 1: 57 | raise Exception("Error: p is not prime in %s!" % (self.__name__)) 58 | 59 | return IntegerModP(x) 60 | 61 | def __abs__(self): 62 | return abs(self.n) 63 | 64 | def __str__(self): 65 | return str(self.n) 66 | 67 | def __repr__(self): 68 | return '%d (mod %d)' % (self.n, self.p) 69 | 70 | def __int__(self): 71 | return self.n 72 | 73 | IntegerModP.p = p 74 | IntegerModP.__name__ = 'Z/%d' % (p) 75 | IntegerModP.englishName = 'IntegersMod%d' % (p) 76 | return IntegerModP 77 | 78 | 79 | if __name__ == "__main__": 80 | mod7 = IntegersModP(7) 81 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/finitefield/numbertype.py: -------------------------------------------------------------------------------- 1 | # memoize calls to the class constructors for fields 2 | # this helps typechecking by never creating two separate 3 | # instances of a number class. 4 | def memoize(f): 5 | cache = {} 6 | 7 | def memoizedFunction(*args, **kwargs): 8 | argTuple = args + tuple(kwargs) 9 | if argTuple not in cache: 10 | cache[argTuple] = f(*args, **kwargs) 11 | return cache[argTuple] 12 | 13 | memoizedFunction.cache = cache 14 | return memoizedFunction 15 | 16 | 17 | # type check a binary operation, and silently typecast 0 or 1 18 | def typecheck(f): 19 | def newF(self, other): 20 | if (hasattr(other.__class__, 'operatorPrecedence') and 21 | other.__class__.operatorPrecedence > self.__class__.operatorPrecedence): 22 | return NotImplemented 23 | 24 | if type(self) is not type(other): 25 | try: 26 | other = self.__class__(other) 27 | except TypeError: 28 | message = 'Not able to typecast %s of type %s to type %s in function %s' 29 | raise TypeError(message % (other, type(other).__name__, type(self).__name__, f.__name__)) 30 | except Exception as e: 31 | message = 'Type error on arguments %r, %r for functon %s. Reason:%s' 32 | raise TypeError(message % (self, other, f.__name__, type(other).__name__, type(self).__name__, e)) 33 | 34 | return f(self, other) 35 | 36 | return newF 37 | 38 | 39 | 40 | # require a subclass to implement +-* neg and to perform typechecks on all of 41 | # the binary operations finally, the __init__ must operate when given a single 42 | # argument, provided that argument is the int zero or one 43 | class DomainElement(object): 44 | operatorPrecedence = 1 45 | 46 | # the 'r'-operators are only used when typecasting ints 47 | def __radd__(self, other): return self + other 48 | def __rsub__(self, other): return -self + other 49 | def __rmul__(self, other): return self * other 50 | 51 | # square-and-multiply algorithm for fast exponentiation 52 | def __pow__(self, n): 53 | if type(n) not in (int,long): 54 | raise TypeError 55 | 56 | Q = self 57 | R = self if n & 1 else self.__class__(1) 58 | 59 | i = 2 60 | while i <= n: 61 | Q = (Q * Q) 62 | 63 | if n & i == i: 64 | R = (Q * R) 65 | 66 | i = i << 1 67 | 68 | return R 69 | 70 | 71 | # requires the additional % operator (i.e. a Euclidean Domain) 72 | def powmod(self, n, modulus): 73 | if type(n) not in (int,long): 74 | raise TypeError 75 | 76 | Q = self 77 | R = self if n & 1 else self.__class__(1) 78 | 79 | i = 2 80 | while i <= n: 81 | Q = (Q * Q) % modulus 82 | 83 | if n & i == i: 84 | R = (Q * R) % modulus 85 | 86 | i = i << 1 87 | 88 | return R 89 | 90 | 91 | 92 | # additionally require inverse() on subclasses 93 | class FieldElement(DomainElement): 94 | def __truediv__(self, other): return self * other.inverse() 95 | def __rtruediv__(self, other): return self.inverse() * other 96 | def __div__(self, other): return self.__truediv__(other) 97 | def __rdiv__(self, other): return self.__rtruediv__(other) 98 | 99 | -------------------------------------------------------------------------------- /elliptic-curves-finite-fields/finitefield/polynomial.py: -------------------------------------------------------------------------------- 1 | try: 2 | from itertools import zip_longest 3 | except ImportError: 4 | from itertools import izip_longest as zip_longest 5 | import fractions 6 | 7 | from .numbertype import * 8 | 9 | # strip all copies of elt from the end of the list 10 | def strip(L, elt): 11 | if len(L) == 0: return L 12 | 13 | i = len(L) - 1 14 | while i >= 0 and L[i] == elt: 15 | i -= 1 16 | 17 | return L[:i+1] 18 | 19 | 20 | # create a polynomial with coefficients in a field; coefficients are in 21 | # increasing order of monomial degree so that, for example, [1,2,3] 22 | # corresponds to 1 + 2x + 3x^2 23 | @memoize 24 | def polynomialsOver(field=fractions.Fraction): 25 | 26 | class Polynomial(DomainElement): 27 | operatorPrecedence = 2 28 | 29 | @classmethod 30 | def factory(cls, L): 31 | return Polynomial([cls.field(x) for x in L]) 32 | 33 | def __init__(self, c): 34 | if type(c) is Polynomial: 35 | self.coefficients = c.coefficients 36 | elif isinstance(c, field): 37 | self.coefficients = [c] 38 | elif not hasattr(c, '__iter__') and not hasattr(c, 'iter'): 39 | self.coefficients = [field(c)] 40 | else: 41 | self.coefficients = c 42 | 43 | self.coefficients = strip(self.coefficients, field(0)) 44 | self.indeterminate = 't' 45 | 46 | 47 | def isZero(self): return self.coefficients == [] 48 | 49 | def __repr__(self): 50 | if self.isZero(): 51 | return '0' 52 | 53 | return ' + '.join(['%s %s^%d' % (a, self.indeterminate, i) if i > 0 else '%s'%a 54 | for i,a in enumerate(self.coefficients)]) 55 | 56 | 57 | def __abs__(self): return len(self.coefficients) # the valuation only gives 0 to the zero polynomial, i.e. 1+degree 58 | def __len__(self): return len(self.coefficients) 59 | def __sub__(self, other): return self + (-other) 60 | def __iter__(self): return iter(self.coefficients) 61 | def __neg__(self): return Polynomial([-a for a in self]) 62 | 63 | def iter(self): return self.__iter__() 64 | def leadingCoefficient(self): return self.coefficients[-1] 65 | def degree(self): return abs(self) - 1 66 | 67 | @typecheck 68 | def __eq__(self, other): 69 | return self.degree() == other.degree() and all([x==y for (x,y) in zip(self, other)]) 70 | 71 | @typecheck 72 | def __ne__(self, other): 73 | return self.degree() != other.degree() or any([x!=y for (x,y) in zip(self, other)]) 74 | 75 | @typecheck 76 | def __add__(self, other): 77 | newCoefficients = [sum(x) for x in zip_longest(self, other, fillvalue=self.field(0))] 78 | return Polynomial(newCoefficients) 79 | 80 | 81 | @typecheck 82 | def __mul__(self, other): 83 | if self.isZero() or other.isZero(): 84 | return Zero() 85 | 86 | newCoeffs = [self.field(0) for _ in range(len(self) + len(other) - 1)] 87 | 88 | for i,a in enumerate(self): 89 | for j,b in enumerate(other): 90 | newCoeffs[i+j] += a*b 91 | 92 | return Polynomial(newCoeffs) 93 | 94 | 95 | @typecheck 96 | def __divmod__(self, divisor): 97 | quotient, remainder = Zero(), self 98 | divisorDeg = divisor.degree() 99 | divisorLC = divisor.leadingCoefficient() 100 | 101 | while remainder.degree() >= divisorDeg: 102 | monomialExponent = remainder.degree() - divisorDeg 103 | monomialZeros = [self.field(0) for _ in range(monomialExponent)] 104 | monomialDivisor = Polynomial(monomialZeros + [remainder.leadingCoefficient() / divisorLC]) 105 | 106 | quotient += monomialDivisor 107 | remainder -= monomialDivisor * divisor 108 | 109 | return quotient, remainder 110 | 111 | 112 | @typecheck 113 | def __truediv__(self, divisor): 114 | if divisor.isZero(): 115 | raise ZeroDivisionError 116 | return divmod(self, divisor)[0] 117 | 118 | 119 | @typecheck 120 | def __mod__(self, divisor): 121 | if divisor.isZero(): 122 | raise ZeroDivisionError 123 | return divmod(self, divisor)[1] 124 | 125 | 126 | def Zero(): 127 | return Polynomial([]) 128 | 129 | 130 | Polynomial.field = field 131 | Polynomial.__name__ = '(%s)[x]' % field.__name__ 132 | Polynomial.englishName = 'Polynomials in one variable over %s' % field.__name__ 133 | return Polynomial 134 | 135 | -------------------------------------------------------------------------------- /secp256k1_openssl.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import ctypes.util 3 | from serialize import uint256_to_str, uint256_from_str 4 | 5 | _ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32') 6 | _ssl.BN_bn2hex.restype = ctypes.c_char_p 7 | # this specifies the curve used with ECDSA. 8 | _NID_secp256k1 = 714 # from openssl/obj_mac.h 9 | 10 | # test that openssl support secp256k1 11 | if _ssl.EC_KEY_new_by_curve_name(_NID_secp256k1) == 0: 12 | errno = _ssl.ERR_get_error() 13 | errmsg = ctypes.create_string_buffer(120) 14 | _ssl.ERR_error_string_n(errno, errmsg, 120) 15 | raise RuntimeError('openssl error: %s' % errmsg.value) 16 | 17 | 18 | # Thx to Sam Devlin for the ctypes magic 64-bit fix. 19 | def _check_result (val, func, args): 20 | if val == 0: 21 | raise ValueError 22 | else: 23 | return ctypes.c_void_p(val) 24 | 25 | _ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p 26 | _ssl.EC_KEY_new_by_curve_name.errcheck = _check_result 27 | 28 | curve = _ssl.EC_KEY_new_by_curve_name(_NID_secp256k1) 29 | group = _ssl.EC_KEY_get0_group(curve) 30 | order = _ssl.BN_new() 31 | _ctx = _ssl.BN_CTX_new() 32 | _ssl.EC_GROUP_get_order(group, order, _ctx) 33 | 34 | class SPoint(object): 35 | _fields_ = [('point',int)] 36 | 37 | def __init__(self, x=None, y=None, ybit=None, _point=None): 38 | assert x is None or type(x) is long 39 | assert ybit is None or type(ybit) is int 40 | if x is None: 41 | assert y is None 42 | assert _point is not None 43 | self.point = _point 44 | return 45 | else: 46 | assert y is not None or ybit is not None 47 | assert _point is None 48 | 49 | self.point = _ssl.EC_POINT_new(group) 50 | ctx = _ssl.BN_CTX_new() 51 | x = _ssl.BN_bin2bn(uint256_to_str(x)[::-1], 32, _ssl.BN_new()) 52 | 53 | if y is not None: 54 | y = _ssl.BN_bin2bn(uint256_to_str(y)[::-1], 32, _ssl.BN_new()) 55 | _ssl.EC_POINT_set_affine_coordinates_GFp(group, self.point, x, y, ctx) 56 | else: 57 | _ssl.EC_POINT_set_compressed_coordinates_GFp(group, self.point, x, y%2, ctx) 58 | 59 | _check = _ssl.EC_POINT_is_on_curve(group, self.point, ctx) 60 | assert _check 61 | 62 | _ssl.BN_CTX_free(ctx) 63 | 64 | def _coords(self): 65 | ctx = _ssl.BN_CTX_new() 66 | x = _ssl.BN_new() 67 | y = _ssl.BN_new() 68 | _ssl.EC_POINT_get_affine_coordinates_GFp(group, self.point, x, y, ctx) 69 | def _bn2bin(bn): 70 | buf = ctypes.create_string_buffer(32) 71 | size = _ssl.BN_bn2bin(bn, ctypes.byref(buf)) 72 | n = uint256_from_str(buf[:size][::-1]+(32-size)*'\x00') 73 | return n 74 | x = _bn2bin(x) 75 | y = _bn2bin(y) 76 | _ssl.BN_CTX_free(ctx) 77 | return x,y 78 | 79 | def mult(self, x): 80 | assert type(x) is long 81 | result = _ssl.EC_POINT_new(group) 82 | ctx = _ssl.BN_CTX_new() 83 | x = _ssl.BN_bin2bn(uint256_to_str(x)[::-1], 32, _ssl.BN_new()) 84 | _ssl.EC_POINT_mul(group, result, None, self.point, x, ctx) 85 | _ssl.BN_CTX_free(ctx) 86 | return SPoint(_point=result) 87 | 88 | def __destroy__(self): 89 | _ssl.EC_POINT_free(self.point) 90 | 91 | def __repr__(self): 92 | return 'SPoint()' 93 | -------------------------------------------------------------------------------- /simple-zk-proofs.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Examples of simple zero-knowledge proofs implemented in Python 3 | # 4 | # More specifically, these are non-interactive, zero-knowledge, 5 | # proofs of knowledge. They can be analyzed and proven secure 6 | # in the random oracle model (the random oracle here is instantiated 7 | # with the SHA2 hash function). 8 | # 9 | # Lecture notes: 10 | # http://soc1024.web.engr.illinois.edu/teaching/ece598am/fall2016/zkproofs.pdf 11 | 12 | 13 | import sys 14 | sys.path += ['elliptic-curves-finite-fields'] 15 | from finitefield.finitefield import FiniteField 16 | from elliptic import GeneralizedEllipticCurve, Point, Ideal 17 | import elliptic 18 | import os 19 | import random 20 | 21 | ## 22 | ## This is the definition of secp256k1, Bitcoin's elliptic curve. 23 | ## You can probably skip this, it's a bunch of well-known numbers 24 | ## 25 | 26 | # First the finite field 27 | q = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 28 | Fq = FiniteField(q,1) # elliptic curve over F_q 29 | 30 | # Then the curve, always of the form y^2 = x^3 + {a6} 31 | curve = GeneralizedEllipticCurve(a6=Fq(7)) # E: y2 = x3+7 32 | 33 | # base point, a generator of the group 34 | Gx = Fq(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) 35 | Gy = Fq(0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) 36 | G = Point(curve, Gx, Gy) 37 | 38 | # This is the order (# of elements in) the curve 39 | p = order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 40 | Fp = FiniteField(p,1) 41 | 42 | 43 | ## 44 | ## Convenience functions 45 | ## 46 | def random_oracle_string_to_Zp(s): 47 | return sha2_to_long(s) % p 48 | 49 | def sha2_to_long(seed): 50 | # BUG: we should replace this with return uint256_from_str(Hash(seed)) 51 | from Crypto.Hash import SHA256 52 | return long(SHA256.new(seed).hexdigest(),16) 53 | 54 | # This easy sqrt works for this curve, not necessarily all curves 55 | # https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus 56 | def sqrt(a): 57 | # p: modulus of the underlying finitefield 58 | return a ** ((q+1)/4) 59 | 60 | def random_point(seed=None): 61 | import os 62 | if seed is None: seed = os.urandom(32) 63 | assert type(seed) == str and len(seed) == 32 64 | x = sha2_to_long(seed) 65 | while True: 66 | try: 67 | p = solve(Fq(x)) 68 | except ValueError: 69 | if curve.testPoint(p.x, p.y): break 70 | seed = Hash('random_point:' + seed) 71 | x = sha2_to_long(seed) 72 | continue 73 | break 74 | return p 75 | 76 | def solve(x): 77 | # Solve for y, given x 78 | # There are two possible points that satisfy the curve, 79 | # an even and an odd. We choose the odd one. 80 | y = sqrt(x**3 + 7) 81 | if y.n % 2 == 0: y = -y 82 | if not curve.testPoint(x, y): raise ValueError 83 | return Point(curve, x, y) 84 | 85 | ## 86 | ## Example ZK Proof 87 | ## 88 | ## This is a discrete log proof of ZKP{ (a): A = g^a } 89 | ## 90 | 91 | def sigma_proof1(a, A): 92 | assert a*G == A 93 | # blinding factor 94 | k = random.randint(0,order) 95 | 96 | # commitment 97 | K = k*G 98 | 99 | # use a hash function instead of communicating w/ verifier 100 | c = random_oracle_string_to_Zp(str(K)) 101 | 102 | # response 103 | s = Fp(k + c*a) 104 | 105 | return (K,s) 106 | 107 | 108 | def verify_proof1(A, prf): 109 | (K,s) = prf 110 | assert type(A) is type(K) is elliptic.Point 111 | assert type(s) is Fp 112 | 113 | # Recompute c w/ the information given 114 | c = sha2_to_long(str(K)) 115 | 116 | assert s.n *G == K + c*A 117 | return True 118 | 119 | def test_proof1(): 120 | # Randomly choose "a" 121 | a = random.randint(0,order) 122 | A = a*G 123 | 124 | prf = sigma_proof1(a, A) 125 | assert verify_proof1(A, prf) 126 | 127 | ## 128 | ## Example: a more complicated discrete log proof 129 | ## Zk{ (a, b): A=g^a, B=g^b, C = g^(a(b+3)) } 130 | ## 131 | ## First rewrite as: 132 | ## Zk{ (a, b): A=g^a, B=g^b, C/A^3 = A^b) } 133 | 134 | def sigma_proof2(a, b, A, B, C): 135 | assert a*G == A 136 | assert b*G == B 137 | assert (a*(b+3))*G == C 138 | # blinding factor 139 | kA = random.randint(0,order) 140 | kB = random.randint(0,order) 141 | 142 | # commitment 143 | KA = kA *G 144 | KB = kB *G 145 | KC = kB *A 146 | 147 | # use a hash function instead of communicating w/ verifier 148 | c = random_oracle_string_to_Zp(str(KA) + str(KB) + str(KC)) 149 | 150 | # response 151 | s1 = Fp(kA + c * a) 152 | s2 = Fp(kB + c * b) 153 | 154 | return (KA,KB,KC,s1,s2) 155 | 156 | def verify_proof2(A, B, C, prf): 157 | (KA,KB,KC,s1,s2) = prf 158 | assert type(KA) == type(KB) == type(KC) == elliptic.Point 159 | assert type(s1) == type(s2) == Fp 160 | 161 | # Recompute c w/ the information given 162 | c = random_oracle_string_to_Zp(str(KA) + str(KB) + str(KC)) 163 | 164 | assert s1.n*G == KA + c*A 165 | assert s2.n*G == KB + c*B 166 | assert s2.n*A == KC + c*(C - 3*A) 167 | return True 168 | 169 | def test_proof2(): 170 | # Randomly choose "a" and "b" 171 | a = random.randint(0,order) 172 | b = random.randint(0,order) 173 | A = a*G 174 | B = b*G 175 | C = (a*(b+3)) * G 176 | 177 | prf = sigma_proof2(a, b, A, B, C) 178 | assert verify_proof2(A, B, C, prf) 179 | --------------------------------------------------------------------------------