# build P lattice MUL = 10 ** 500 LP = Matrix(ZZ, _n - 1, _n - 1) LP[0] = vector(ZZ, [0if i % 2else _p * MUL for i in range(_n - 1)]) for i in range(1, _n - 1): tmp_vec = [] for j in range(1, g + 1): tmp_vec.append(RR((2 / _n * i * j * pi).cos() * MUL).round() - int(pow(a, i, _p)) * MUL) tmp_vec.append(RR((2 / _n * i * j * pi).sin() * MUL).round()) LP[i] = vector(ZZ, tmp_vec)
# reduce the lattice LPL = LP.LLL()
# get beta coefficients of Z-basis, then get beta coefficients = LP.transpose().solve_right(LPL[0]) tmp = _p * coefficients[0] for i in range(1, _n - 1): tmp -= int(pow(a, i, _p)) * coefficients[i] beta = K([tmp] + coefficients[1:].list()) assert beta.norm() % _p == 0 if beta.norm() != _p: c = beta.norm() // _p # brute force for a y has norm c for i in range(3 ** g): y = [0] * g for j in range(g): y.append(i % 3) i //= 3 if K(y).norm() == c: beta /= K(y) break assert beta.norm() == _p
# calculate J(X, X) sigma = K.automorphisms() J_hat = 1 for i in range((_n - 1) // 2): J_hat *= sigma[i].preimage(beta) coefficients = J_hat.polynomial().coefficients() r = -int(sum(coefficients)) % _n if r != 1: assert r == _n - 1 r = -1 s = int(r * sum([coefficients[j] * j for j in range(_n - 1)])) % _n J_X_X = r * z ** s * J_hat
# calculate twist i, j and get order if constant != 0: i = 1 else: i = 0 for j in range(_n): if pow(a, j, _p) == pow((-1) ** i * x_n_coefficient, (_p - 1) // _n, _p): return int((J_X_X + (-1) ** i * z ** j).norm())
deflattice_reduction_HCDSA_HNP_attack(_Sf, _q): bits = _q.bit_length() num = len(_Sf) S = [sfi[1] for sfi in _Sf] f = [sfi[0] for sfi in _Sf] fb = [list(map(int, bin(f[i])[2:].rjust(bits, '0')))[::-1] for i in range(num)] check_zero = sum(vector(ZZ, fbi) for fbi in fb).list() _zero_index = [] for i in range(bits): if check_zero[i] == 0: _zero_index.append(i) for k0 in range(bits-3): for k1 in range(k0+1, k0+3): is_zero_index = True for i in range(num): ifnot is_zero_index: break for j in range(i+1, num): if fb[j][k0] - fb[i][k0] + fb[j][k1] - fb[i][k1] != 0: is_zero_index = False break if is_zero_index: _zero_index.extend([k0, k1]) _zero_index = list(set(_zero_index))
# calculate beta beta = [] for i in range(num - 3): for j in range(i + 1, num - 2): for m in range(j + 1, num - 1): for n in range(m + 1, num): beta.append([]) for k in range(bits): beta[-1].append(2 ** k * ((S[m] - S[n]) * (fb[j][k] - fb[i][k]) - (S[i] - S[j]) * (fb[n][k] - fb[m][k])))
# calculate the least number of beta use_num = 45 got = False whilenot got: use_num += 1 for i in range(20): shuffle(beta) if sum(vector(ZZ, beta_i) for beta_i in beta[:use_num]).list().count(0) == len(_zero_index): got = True break
# build lattice # K = 2**bits K = 1 M = Matrix(ZZ, bits + use_num, bits + use_num) for i in range(use_num): M[:, i] = K * vector(ZZ, beta[i] + [0] * i + [q] + [0] * (use_num - i - 1)) M[:bits, use_num:use_num + bits] = identity_matrix(ZZ, bits) # M.delete_rows(_zero_index)
# reduce lattice and get d start = time.time() ML = M.LLL() end = time.time() print('use %d seconds' % int(end - start)) for l in ML: if l[:use_num].is_zero(): tmp_db = l[use_num:use_num + bits][::-1] if set(tmp_db) != {-1, 0, 1}: continue _db0 = ['1'if dbi == 1else'0'for dbi in tmp_db] _db1 = ['0'if dbi == 1else'1'for dbi in tmp_db] for i in range(bits): if tmp_db[i] == 0and bits-1-i notin _zero_index: _zero_index.append(bits-1-i) for i in _zero_index: _db0[bits-1-i] = '0' _db1[bits-1-i] = '0' return [int(''.join(_db0), 2), int(''.join(_db1), 2)], _zero_index
# get order p = 109912987917332562152579558699996713895127464171381146718513726836854765335393 PRp = PolynomialRing(GF(p), 'x') x = PRp.gens()[0] C2 = HyperellipticCurve(114514 * x ** 7, 1) J2 = C2.jacobian() G = [x - 1195618977299087187547891775318892467576479852329251168026008209153499647043, 61722058527536369025641486069921773295057337558818637400938099789108164705978] G = J2(*G) q = lattice_reduction_get_order(p, 7, 0, 114514) assert q * G == J2(0)
# get most bits of d Sf = all_d, zero_index = lattice_reduction_HCDSA_HNP_attack(Sf, q)
# # check correctness and get flag msg = 'Welcome to HUFU CTF 2022 Quals!' for possible_error in range(2**len(zero_index)): error_bit_array = list(map(int, bin(possible_error)[2:].rjust(len(zero_index), '0'))) error = 0 for index in range(len(zero_index)): error += 2**zero_index[index] * error_bit_array[index] for possible_d in all_d: d = possible_d + error R = H(str(d) + msg) * G if (H(str(d) + msg) + H(str(R) + str(d*G) + msg) * (d ^^ Sf[0][0])) % q == Sf[0][1]: print('d =', d) print('Flag of "HCDSA": HFCTF{{{}}}'.format(sha256(str(d).encode()).hexdigest())) break
defcalculate_order(p, n, f0, f1, genus): # h(x) = 1 # f(x) = y^5 + y^3 + f1 * y + f0
# Calc M_i M = [] for i in range(genus): GFpi = GF(p ** (i + 1)) Ppn = PolynomialRing(GFpi, 'w') w = Ppn.gens()[0] if i == 0: _f0 = f0.integer_representation() % p _f1 = f1.integer_representation() % p else: _f0 = GFpi.fetch_int(f0.integer_representation() % p ** (i + 1)) _f1 = GFpi.fetch_int(f1.integer_representation() % p ** (i + 1)) C = HyperellipticCurve(w ^ 5 + w ^ 3 + _f1 * w + _f0, 1) M.append(C.cardinality()) print("Calc M done")
# Calc a_i a = [0] for i in range(genus): a.append((M[i] - 1 - p ^ (i + 1) + a[i] ^ (i + 1)) / (i + 1)) a = [1] + a[1:] print("Calc a done")
# Calc gamma_i var('X') function = -genus * p for i in range(genus + 1): function += a[i] * X ^ (genus - i) gamma = list(map(lambda y: y.rhs(), solve([function == 0], X))) print("Calc gamma done")
# Calc alpha_i alpha = [] for i in range(genus): function = X ^ 2 - gamma[i] * X + p alpha.append(list(map(lambda y: y.rhs(), solve([function == 0], X)))) print("Calc alpha done")
# Calc #J order = 1 for i in range(genus): order *= abs(1 - alpha[i][0] ^ n) ^ 2 return int(order)
defsolve_quadratic_equation_GF2n(c, m): # solve v**2 + v = c on 2**m v = 0 for i in range((m + 1) // 2): v += c ** (2 ** (2 * i)) return GF2_ext(v)
defparse(point, shift, bits): pad = bits - 2 * shift - 1 assert pad > 0 _x, _y = point n = (_x[0].integer_representation() << shift) + _x[1].integer_representation() n = (n << pad) + randint(1, 2 ^ pad) n = (n << 1) + int(_y[0].integer_representation() % 2) return n
defre_parse(n, shift, bits): pad = bits - 2 * shift - 1 symbol = n % 2 n >>= 1 n >>= pad x1 = n % 2 ** shift x0 = n >> shift u = PR2_ext([GF2_ext.fetch_int(x0), GF2_ext.fetch_int(x1), 1]) assert u.is_irreducible() v = get_v_from_u(u, 0, 1) if v[0].integer_representation() % 2 != symbol: v = PR2_ext([v[0] + 1, v[1]]) return J1([u, PR2_ext(v)])
defGF2n_Ph(_m, _g, _h, _order): _a = [] for mi in _m: tmp_order = _order // mi _a.append(pari.fflog(_h ** tmp_order, _g ** tmp_order, mi)) print(mi, _a) return crt(_a, _m)
Alice_pk = [x ^ 2 + (z ^ 111 + z ^ 107 + z ^ 106 + z ^ 104 + z ^ 102 + z ^ 100 + z ^ 97 + z ^ 95 + z ^ 94 + z ^ 93 + z ^ 92 + z ^ 89 + z ^ 88 + z ^ 84 + z ^ 83 + z ^ 77 + z ^ 76 + z ^ 72 + z ^ 69 + z ^ 68 + z ^ 66 + z ^ 62 + z ^ 61 + z ^ 60 + z ^ 59 + z ^ 58 + z ^ 56 + z ^ 55 + z ^ 54 + z ^ 53 + z ^ 50 + z ^ 47 + z ^ 46 + z ^ 45 + z ^ 43 + z ^ 42 + z ^ 39 + z ^ 38 + z ^ 35 + z ^ 34 + z ^ 32 + z ^ 31 + z ^ 25 + z ^ 24 + z ^ 23 + z ^ 21 + z ^ 19 + z ^ 18 + z ^ 17 + z ^ 16 + z ^ 15 + z ^ 13 + z ^ 12 + z ^ 11 + z ^ 7 + z ^ 5 + z ^ 3) * x + z ^ 112 + z ^ 111 + z ^ 110 + z ^ 107 + z ^ 106 + z ^ 102 + z ^ 100 + z ^ 97 + z ^ 96 + z ^ 95 + z ^ 94 + z ^ 90 + z ^ 86 + z ^ 85 + z ^ 84 + z ^ 82 + z ^ 79 + z ^ 78 + z ^ 77 + z ^ 76 + z ^ 75 + z ^ 74 + z ^ 71 + z ^ 69 + z ^ 64 + z ^ 62 + z ^ 61 + z ^ 58 + z ^ 56 + z ^ 55 + z ^ 52 + z ^ 51 + z ^ 49 + z ^ 47 + z ^ 44 + z ^ 43 + z ^ 41 + z ^ 33 + z ^ 29 + z ^ 28 + z ^ 27 + z ^ 26 + z ^ 25 + z ^ 24 + z ^ 23 + z ^ 21 + z ^ 18 + z ^ 17 + z ^ 15 + z ^ 12 + z ^ 11 + z ^ 10 + z ^ 9 + z ^ 7 + z ^ 4 + z ^ 3 + z + 1, (z ^ 112 + z ^ 110 + z ^ 109 + z ^ 103 + z ^ 102 + z ^ 101 + z ^ 100 + z ^ 99 + z ^ 97 + z ^ 96 + z ^ 95 + z ^ 94 + z ^ 93 + z ^ 92 + z ^ 89 + z ^ 88 + z ^ 86 + z ^ 84 + z ^ 82 + z ^ 80 + z ^ 79 + z ^ 75 + z ^ 74 + z ^ 73 + z ^ 68 + z ^ 64 + z ^ 60 + z ^ 59 + z ^ 55 + z ^ 50 + z ^ 48 + z ^ 46 + z ^ 43 + z ^ 42 + z ^ 39 + z ^ 38 + z ^ 37 + z ^ 36 + z ^ 35 + z ^ 34 + z ^ 28 + z ^ 25 + z ^ 23 + z ^ 22 + z ^ 20 + z ^ 17 + z ^ 15 + z ^ 13 + z ^ 10 + z ^ 3 + z) * x + z ^ 111 + z ^ 110 + z ^ 109 + z ^ 106 + z ^ 105 + z ^ 102 + z ^ 93 + z ^ 90 + z ^ 89 + z ^ 88 + z ^ 86 + z ^ 84 + z ^ 83 + z ^ 81 + z ^ 80 + z ^ 77 + z ^ 76 + z ^ 74 + z ^ 73 + z ^ 72 + z ^ 70 + z ^ 68 + z ^ 66 + z ^ 64 + z ^ 61 + z ^ 60 + z ^ 59 + z ^ 58 + z ^ 57 + z ^ 50 + z ^ 49 + z ^ 47 + z ^ 46 + z ^ 45 + z ^ 44 + z ^ 43 + z ^ 42 + z ^ 39 + z ^ 38 + z ^ 36 + z ^ 33 + z ^ 32 + z ^ 31 + z ^ 29 + z ^ 28 + z ^ 26 + z ^ 25 + z ^ 24 + z ^ 17 + z ^ 15 + z ^ 13 + z ^ 10 + z ^ 9 + z ^ 8 + z ^ 5 + z ^ 4 + z ^ 3 + z ^ 2 + z] Alice_pk = J1(*Alice_pk) q = ZZ(calculate_order(2, 113, z - z, z - z + 1, 2)) assert q * Alice_pk == J1(0) # q = 7 * 10113049 * 320021624768405574452943847 * 4760137992283599860814226997712217 for k in range(2, 12): if (2 ** (113 * k) - 1) % q == 0: break
d = 402045457200978546368835668950711933106933538070552823738108748139869490234501184225175369065216758931906216168485716846341035514282333269655203225744353550872749071863611510874494552527466229826353578228691696842853401202429961474 Shared_key = re_parse(d, 113, 767) check_d = parse(Shared_key, 113, 767) assert check_d >> 541 == d >> 541and check_d % 2 == d % 2