def untwist(P): q = P.X.q root = Fq6(Fq2.zero(q), Fq2.one(q), Fq2.zero(q)) zero = Fq6.zero(q) omega2 = Fq12(root, zero) omega3 = Fq12(zero, root) return EC.from_affine(omega2.inverse() * P.x, omega3.inverse() * P.y)
def twist(P): q = P.X.q root = Fq6(Fq2.zero(q), Fq2.one(q), Fq2.zero(q)) zero = Fq6.zero(q) omega2 = Fq12(root, zero) omega3 = Fq12(zero, root) c0 = omega2 * P.x c1 = omega3 * P.y return TwistedEC.from_affine(c0.c0.c0, c1.c0.c0)
def untwist(point: AffinePoint, ec=default_ec) -> AffinePoint: """ Given a point on G2 on the twisted curve, this converts it's coordinates back from Fq2 to Fq12. See Craig Costello book, look up twists. """ f = Fq12.one(ec.q) wsq = Fq12(ec.q, f.root, Fq6.zero(ec.q)) wcu = Fq12(ec.q, Fq6.zero(ec.q), f.root) return AffinePoint(point.x / wsq, point.y / wcu, False, ec)
def twist(point: AffinePoint, ec=default_ec_twist) -> AffinePoint: """ Given an untwisted point, this converts it's coordinates to a point on the twisted curve. See Craig Costello book, look up twists. """ f = Fq12.one(ec.q) wsq = Fq12(ec.q, f.root, Fq6.zero(ec.q)) wcu = Fq12(ec.q, Fq6.zero(ec.q), f.root) new_x = point.x * wsq new_y = point.y * wcu return AffinePoint(new_x, new_y, False, ec)
def test_fields(): a = Fq(17, 30) b = Fq(17, -18) c = Fq2(17, a, b) d = Fq2(17, a + a, -5) e = c * d f = e * d assert f != e e_sq = e * e e_sqrt = e_sq.modsqrt() assert pow(e_sqrt, 2) == e_sq a2 = Fq( 172487123095712930573140951348, 3012492130751239573498573249085723940848571098237509182375, ) b2 = Fq(172487123095712930573140951348, 3432984572394572309458723045723849) c2 = Fq2(172487123095712930573140951348, a2, b2) assert b2 != c2 g = Fq6(17, c, d, d * d * c) h = Fq6(17, a + a * c, c * b * a, b * b * d * 21) i = Fq12(17, g, h) assert ~(~i) == i assert (~(i.root)) * i.root == Fq6.one(17) x = Fq12(17, Fq6.zero(17), i.root) assert (~x) * x == Fq12.one(17) j = Fq6(17, a + a * c, Fq2.zero(17), Fq2.zero(17)) j2 = Fq6(17, a + a * c, Fq2.zero(17), Fq2.one(17)) assert j == (a + a * c) assert j2 != (a + a * c) assert j != j2 # Test frob_coeffs one = Fq(default_ec.q, 1) two = one + one a = Fq2(default_ec.q, two, two) b = Fq6(default_ec.q, a, a, a) c = Fq12(default_ec.q, b, b) for base in (a, b, c): for expo in range(1, base.extension): assert base.qi_power(expo) == pow(base, pow(default_ec.q, expo))
def _miller_loop(T, P, Q): if len(P) == 3: P = from_jacobian(P) res_num = Fq12.one(p) res_den = Fq12.one(p) R = Q T_bits = [1 if b == '1' else 0 for b in bin(T)[3:]] # all except MSB in MSB-to-LSB order for b in T_bits: (d_num, d_den) = _double_eval(R, P) res_num = pow(res_num, 2) * d_num res_den = pow(res_den, 2) * d_den R = point_double(R) if b: (a_num, a_den) = _add_eval(R, Q, P) res_num = res_num * a_num res_den = res_den * a_den R = point_add(R, Q) return res_num / res_den
def pop_verify(pk: JacobianPoint, proof: JacobianPoint) -> bool: try: proof.check_valid() pk.check_valid() q = g2_map(bytes(pk), pop_scheme_pop_dst) one = Fq12.one(default_ec.q) pairing_result = ate_pairing_multi([pk, G1Generator().negate()], [q, proof]) return pairing_result == one except AssertionError: return False
def ate_pairing_multi(Ps, Qs, ec=default_ec): """ Computes multiple pairings at once. This is more efficient, since we can multiply all the results of the miller loops, and perform just one final exponentiation. """ t = default_ec.x + 1 T = abs(t - 1) prod = Fq12.one(ec.q) for i in range(len(Qs)): prod *= miller_loop(T, Ps[i], Qs[i], ec) return final_exponentiation(prod, ec)
def core_verify_mpl(pk: JacobianPoint, message: bytes, signature: JacobianPoint, dst: bytes) -> bool: try: signature.check_valid() pk.check_valid() except AssertionError: return False q = g2_map(message, dst) one = Fq12.one(default_ec.q) pairing_result = ate_pairing_multi([pk, G1Generator().negate()], [q, signature]) return pairing_result == one
def setUp(self): self.a1 = Fq(3, 11) self.b1 = Fq(4, 11) self.c1 = Fq(7, 11) self.d1 = Fq(12, 11) self.e1 = Fq(2, 11) self.f1 = Fq(9, 11) self.g1 = Fq(6, 11) self.a2 = Fq2(self.a1, self.b1) self.b2 = Fq2(self.b1, self.c1) self.c2 = Fq2(self.c1, self.d1) self.d2 = Fq2(self.d1, self.e1) self.e2 = Fq2(self.e1, self.f1) self.f2 = Fq2(self.f1, self.g1) self.a6 = Fq6(self.a2, self.b2, self.c2) self.b6 = Fq6(self.b2, self.c2, self.d2) self.c6 = Fq6(self.d2, self.e2, self.f2) self.a12 = Fq12(self.a6, self.b6) self.b12 = Fq12(self.b6, self.c6)
def verify(self): """ This implementation of verify has several steps. First, it reorganizes the pubkeys and messages into groups, where each group corresponds to a message. Then, it checks if the siganture has info on how it was aggregated. If so, we exponentiate each pk based on the exponent in the AggregationInfo. If not, we find public keys that share messages with others, and aggregate all of these securely (with exponents.). Finally, since each public key now corresponds to a unique message (since we grouped them), we can verify using the distinct verification procedure. """ message_hashes = self.aggregation_info.message_hashes public_keys = self.aggregation_info.public_keys assert (len(message_hashes) == len(public_keys)) hash_to_public_keys = {} for i in range(len(message_hashes)): if message_hashes[i] in hash_to_public_keys: hash_to_public_keys[message_hashes[i]].append(public_keys[i]) else: hash_to_public_keys[message_hashes[i]] = [public_keys[i]] final_message_hashes = [] final_public_keys = [] ec = public_keys[0].value.ec for message_hash, mapped_keys in hash_to_public_keys.items(): dedup = list(set(mapped_keys)) public_key_sum = JacobianPoint(Fq.one(ec.q), Fq.one(ec.q), Fq.zero(ec.q), True, ec) for public_key in dedup: try: exponent = self.aggregation_info.tree[(message_hash, public_key)] public_key_sum += (public_key.value * exponent) except KeyError: return False final_message_hashes.append(message_hash) final_public_keys.append(public_key_sum.to_affine()) mapped_hashes = [ hash_to_point_prehashed_Fq2(mh) for mh in final_message_hashes ] g1 = Fq(default_ec.n, -1) * generator_Fq() Ps = [g1] + final_public_keys Qs = [self.value.to_affine()] + mapped_hashes res = ate_pairing_multi(Ps, Qs, default_ec) return res == Fq12.one(default_ec.q)
def millers_alg(P, Q): R = Q f = Fq12.one(params.q) for r_i in bin(params.BLS_x)[3:]: l_RR = line(R, R, P) f = f.square() * l_RR R *= 2 if r_i == '1': l_RQ = line(R, Q, P) f *= l_RQ R += Q if params.BLS_negative: f = -f return f
def core_aggregate_verify(pks: List[JacobianPoint], ms: List[bytes], signature: JacobianPoint, dst: bytes) -> bool: if len(pks) != len(ms) or len(pks) < 1: return False try: signature.check_valid() qs = [signature] ps = [G1Generator().negate()] for i in range(len(pks)): pks[i].check_valid() qs.append(g2_map(ms[i], dst)) ps.append(pks[i]) return Fq12.one(default_ec.q) == ate_pairing_multi(ps, qs) except AssertionError: return False
def verify(self, message_hashes, public_keys): """ Verifies messages using the prepend method. It prepends public keys to message hashes before verifying. """ assert (len(message_hashes) == len(public_keys)) mapped_hashes = [ hash_to_point_prehashed_Fq2( hash256(public_keys[i].serialize() + message_hashes[i])) for i in range(len(message_hashes)) ] keys = [pk.value.to_affine() for pk in public_keys] g1 = Fq(default_ec.n, -1) * generator_Fq() Ps = [g1] + keys Qs = [self.value.to_affine()] + mapped_hashes res = ate_pairing_multi(Ps, Qs, default_ec) return res == Fq12.one(default_ec.q)
def miller_loop(T, P, Q, ec=default_ec): """ Performs a double and add algorithm for the ate pairing. This algorithm is taken from Craig Costello's "Pairing for Beginners". """ T_bits = int_to_bits(T) R = Q f = Fq12.one(ec.q) # f is an element of Fq12 for i in range(1, len(T_bits)): # Compute sloped line lrr lrr = double_line_eval(R, P, ec) f = f * f * lrr R = 2 * R if T_bits[i] == 1: # Compute sloped line lrq lrq = add_line_eval(R, Q, P, ec) f = f * lrq R = R + Q return f
def multi_pairing(Ps, Qs): assert all(isinstance(pp, Fq) for P in Ps for pp in P) assert all(isinstance(pp, Fq2) for Q in Qs for pp in Q) return _final_exp( reduce(mul, (_miller_loop(abs(ell_u), P, Q) for (P, Q) in zip(Ps, Qs)), Fq12.one(p)))
# Changes from the original version: # * uses curve impl from curve_ops # * only supports BLS12-381 # * Miller loop implementation avoids computing inversions # # (C) 2019 Riad S. Wahby <*****@*****.**> from functools import reduce from operator import mul from consts import p, ell_u, k_final from curve_ops import from_jacobian, point_double, point_add, to_coZ from fields import Fq, Fq2, Fq6, Fq12 # constants for untwisting ut_root = Fq12.one(p).root ut_wsq_inv = ~Fq12(p, ut_root, Fq6.zero(p)) ut_wcu_inv = ~Fq12(p, Fq6.zero(p), ut_root) del ut_root def _untwist(R): assert all(isinstance(pp, Fq2) for pp in R) (x, y, z) = R return (x * ut_wsq_inv, y * ut_wcu_inv, z) def _double_eval(R, P): (xP, yP) = P (xR, yR, zR) = _untwist(R) zR3 = pow(zR, 3)