def double(cls, p): res = [None] * 3 if p == [FQ(0), FQ(1), FQ(0)]: return p # A = X1^2 # B = Y1^2 # C = B^2 A = FQ(p[0])**2 B = FQ(p[1])**2 C = B**2 # D = 2 * ((X1 + B)^2 - A - C) D = 2 * (((p[0] + B)**2) - A - C) E = 3 * A F = E**2 res[0] = F - 2 * D eightC = C * 8 # Y3 = E * (D - X3) - 8 * C res[1] = E * (D - res[0]) - eightC Y1Z1 = p[1] * p[2] res[2] = Y1Z1 * 2 return res
def add(self, other): assert isinstance(other, Point) if self.x == 0 and self.y == 0: return other (u1, v1) = (self.x, self.y) (u2, v2) = (other.x, other.y) u3 = (u1 * v2 + v1 * u2) / (FQ.one() + JUBJUB_D * u1 * u2 * v1 * v2) v3 = (v1 * v2 - JUBJUB_A * u1 * u2) / (FQ.one() - JUBJUB_D * u1 * u2 * v1 * v2) return Point(u3, v3)
def affine(cls, p) -> "BN128": if p == [FQ(0), FQ(1), FQ(0)]: return self.zero else: z_inv = 1 / p[0] z2_inv = z_inv**2 z3_inv = z_inv * z2_inv print("z_inv : ", z_inv) print("z2_inv : ", z2_inv) print("z3_inv : ", z3_inv) res = [None] * 3 res[0] = p[0] * z2_inv res[1] = p[1] * z3_inv res[2] = FQ(1) return res
def from_x(cls, x): """ y^2 = ((a * x^2) / (d * x^2 - 1)) - (1 / (d * x^2 - 1)) For every x coordinate, there are two possible points: (x, y) and (x, -y) """ assert isinstance(x, FQ) xsq = x * x ax2 = JUBJUB_A * xsq dxsqm1 = prime_field_inv(JUBJUB_D * xsq - 1, JUBJUB_Q) ysq = dxsqm1 * (ax2 - 1) y = FQ(square_root_mod_prime(int(ysq), JUBJUB_Q)) return cls(x, y)
def from_y(cls, y, sign=None): """ x^2 = (y^2 - 1) / (d * y^2 - a) """ assert isinstance(y, FQ) ysq = y * y lhs = ysq - 1 rhs = JUBJUB_D * ysq - JUBJUB_A xsq = lhs / rhs x = FQ(square_root_mod_prime(int(xsq), JUBJUB_Q)) if sign is not None: # Used for compress & decompress if (x.n & 1) != sign: x = -x else: if is_negative(x): x = -x return cls(x, y)
def encryption_test(): # K = k*G (pubKey: K, signKey : k, generator : G) # # Encryption # 1. C1 = M + r*K # 2. C2 = r*G # # Decryption # 3. M = C1 - k*C2 # # why? # -> C1 = M + r*K # -> M = C1 - r*K --> It fails? # -> M = C1 - r*k*G # -> M = C1 - k*C2 G = Point.generator() message = 231315 # ascii 98 = b message_field = FQ(message) M = Point.from_y(message_field) k = 1200 K = G * k # K = k*G, public_key print("Public Key : {}".format(K)) ### Encryption Process ### Message(M) is encrypted with public_key(K) and random_number(r) ### output : C1, C2 r = 1929319 #random number print("Random Number : {}".format(r)) C1 = M + (K*r) # C1 = M + r*K C2 = G * r # C2 = r*G print("C1 : {}".format(C1)) print("C2 : {}".format(C2)) ### Decryption Process decrypted_M = C1 - (C2*k); # M = C1 - k*C2 print("Decrypted Message : {}".format(decrypted_M.y))
def from_hash(cls, entropy): """ HashToPoint (or Point.from_hash) Parameters: entropy (bytes): input entropy provided as byte array Hashes the input entropy and interprets the result as the Y coordinate then recovers the X coordinate, if no valid point can be recovered Y is incremented until a matching X coordinate is found. The point is guaranteed to be prime order and not the identity. From: https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/?include_text=1 Page 6: o HashToBase(x, i). This method is parametrized by p and H, where p is the prime order of the base field Fp, and H is a cryptographic hash function which outputs at least floor(log2(p)) + 2 bits. The function first hashes x, converts the result to an integer, and reduces modulo p to give an element of Fp. """ from hashlib import sha256 assert isinstance(entropy, bytes) entropy = sha256(entropy).digest() entropy_as_int = int.from_bytes(entropy, "big") y = FQ(entropy_as_int) while True: try: p = cls.from_y(y) except SquareRootError: y += 1 continue # Multiply point by cofactor, ensures it's on the prime-order subgroup p = p * JUBJUB_C # Verify point is on prime-ordered sub-group if (p * JUBJUB_L) != Point.infinity(): raise RuntimeError("Point not on prime-ordered subgroup") return p
def decompress(cls, point): """ From: https://ed25519.cr.yp.to/eddsa-20150704.pdf The encoding of F_q is used to define "negative" elements of F_q: specifically, x is negative if the (b-1)-bit encoding of x is lexiographically larger than the (b-1)-bit encoding of -x. In particular, if q is prime and the (b-1)-bit encoding of F_q is the little-endian encoding of {0, 1, ..., q-1}, then {1,3,5,...,q-2} are the negative element of F_q. This encoding is also used to define a b-bit encoding of each element `(x,y) ∈ E` as a b-bit string (x,y), namely the (b-1)-bit encoding of y followed by the sign bit. the sign bit is 1 if and only if x is negative. A parser recovers `(x,y)` from a b-bit string, while also verifying that `(x,y) ∈ E`, as follows: parse the first b-1 bits as y, compute `xx = (y^2 - 1) / (dy^2 - a)`; compute `x = [+ or -] sqrt(xx)` where the `[+ or -]` is chosen so that the sign of `x` matches the `b`th bit of the string. if `xx` is not a square then parsing fails. """ if len(point) != 32: raise ValueError("Invalid input length for decompression") # y = int.from_bytes(point, "little") y = int.from_bytes(point, "big") sign = y >> 255 y &= (1 << 255) - 1 return cls.from_y(FQ(y), sign)
def add(cls, p1, p2): if p1 == [FQ(0), FQ(1), FQ(0)]: return p2 if p2 == [FQ(0), FQ(1), FQ(0)]: return p1 res = [None] * 3 Z1Z1 = p1[2]**2 Z2Z2 = p2[2]**2 U1 = p1[0] * Z2Z2 #U1 = X1 * Z2Z2 U2 = p2[0] * Z1Z1 #U2 = X2 * Z1Z1 Z1_cubed = p1[2] * Z1Z1 Z2_cubed = p2[2] * Z2Z2 S1 = p1[1] * Z2_cubed #S1 = Y1 * Z2 * Z2Z2 S2 = p[1] * Z1_cubed #S2 = Y2 * Z1 * Z1Z1 if U1 == U2 and S1 == S2: cls.double(p1) H = U2 - U1 S2_minus_S1 = S2 - S1 I = (H * 2)**2 J = H * I r = S2_minus_S1 + S2_minus_S1 V = U1 * I S1_J = S1 * J # X3 = r^2 - J - 2 * V # Y3 = r * (V-X3)-2*S1*J # Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2) * H res[0] = r**2 - J - 2 * V res[1] = r * (V - res[0]) - (2 * S1_J) res[2] = ((p1[2] + p2[2])**2 - Z1Z1 - Z2Z2) * H return res
def __init__(self, g): self.g = g if len(g) == 2: this.g.append(FQ(1)) self.zero = [FQ(0), FQ(1), FQ(0)]
# res = FQ(0) # rem = FQ(e) # exp = base # # while rem == FQ(0): # if rem + FQ(0) == FQ(1): # res = res + exp # exp = exp ** 2 # rem = rem >> 1 # # return res if __name__ == "__main__": # affine test : it works! p = [FQ(1), FQ(2), FQ(1)] bn = BN128(p) p_affine = bn.affine(p) print(p_affine) # double() test : it works! double_p = bn.double(p) print(bn.double(p)) print(bn.double(double_p)) # add() test : it works! print("bn.add(p, p) : ", bn.add(double_p, p)) # mul_scalar test : test failed # p_mul = bn.mul_scalar(p, 151) # print(p_mul)
if pt is None: return None x, y = pt fq12_point = (FQ12([x.n] + [0] * 11), FQ12([y.n] + [0] * 11)) return fq12_point # Check consistency of the "line function" one, two, three = G1, double(G1), multiply(G1, 3) negone, negtwo, negthree = ( multiply(G1, curve_order - 1), multiply(G1, curve_order - 2), multiply(G1, curve_order - 3), ) assert linefunc(one, two, one) == FQ(0) assert linefunc(one, two, two) == FQ(0) assert linefunc(one, two, three) != FQ(0) assert linefunc(one, two, negthree) == FQ(0) assert linefunc(one, negone, one) == FQ(0) assert linefunc(one, negone, negone) == FQ(0) assert linefunc(one, negone, two) != FQ(0) assert linefunc(one, one, one) == FQ(0) assert linefunc(one, one, two) != FQ(0) assert linefunc(one, one, negtwo) == FQ(0) # Main miller loop def miller_loop(Q: Point2D[FQ12], P: Point2D[FQ12]) -> FQ12: if Q is None or P is None: return FQ12.one()
def infinity(): return Point(FQ(0), FQ(1))
def generator(cls): x = 16540640123574156134436876038791482806971768689494387082833631921987005038935 y = 20819045374670962167435360035096875258406992893633759881276124905556507972311 return Point(FQ(x), FQ(y))
FQ12, ) Point2D = Optional[Tuple[Field, Field]] # Point at infinity is encoded as a None Point3D = Optional[Tuple[Field, Field, Field]] # Point at infinity is encoded as a None GeneralPoint = Union[Point2D[Field], Point3D[Field]] # Curve order should be prime assert pow(2, curve_order, curve_order) == 2 # Curve order should be a factor of field_modulus**12 - 1 assert (field_modulus**12 - 1) % curve_order == 0 # Curve is y**2 = x**3 + 3 b = FQ(3) # Twisted curve over FQ**2 b2 = FQ2([3, 0]) / FQ2([9, 1]) # Extension curve over FQ**12; same b value as over FQ b12 = FQ12([3] + [0] * 11) # Generator for curve over FQ G1 = (FQ(1), FQ(2)) # Generator for twisted curve over FQ2 G2 = ( FQ2([ 10857046999023057135944570762232829481370756359578518086990519993285655852781, 11559732032986387107991004021392285783925812861821192530917403151452391805634, ]), FQ2([ 8495653923123431417604973247489272438418190587263600148770280649306958101930,