1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
| from __future__ import annotations from hashlib import sha256 from Crypto.Cipher import AES from tqdm import tqdm
CT = '75bd1089b2248540e3406aa014dc2b5add4fb83ffdc54d09beb878bbb0d42717e9cc6114311767dd9f3b8b070b359a1ac2eb695cd31f435680ea885e85690f89' EXCLUDED = 381404224402842
two_digit_pairs = [(a, b, a*b, str(a*b)) for a in range(2, 10) for b in range(2, 10) if a*b >= 10]
two_digit_pairs.sort(key=lambda x: -x[2])
one_digit_pairs = [(a, b, a*b, str(a*b)) for a in range(0, 10) for b in range(0, 10) if a*b <= 9]
one_digit_pairs.sort(key=lambda x: -x[2])
def tail_digits_from_blocks(blocks_low_to_high: list[str]) -> list[int]: """blocks_low_to_high: 低位到高位的块字符串;拼接成 tail 再转低位 digits 列表""" if not blocks_low_to_high: return [] s = "".join(reversed(blocks_low_to_high)) return list(map(int, reversed(s)))
def build_known_and_unknown_bounds(lx: list[int], ly: list[int], L_tail: int): """ 计算: known_sum[e]: 所有已知对 (xi,yj) 且 i+j=e 的 xi*yj 之和 U_e_max[e]: 位 e 可能由尚未确定的对贡献的最大增量上界 (使用 81 保守估计) 这里只对 e < L_tail 的低位关心。 """ t = len(lx) max_e = L_tail - 1 if max_e < 0: return [], [] known_sum = [0]*(max_e+1) U_e_max = [0]*(max_e+1) for i in range(t): for j in range(t): e = i + j if e <= max_e: known_sum[e] += lx[i]*ly[j] for i in range(8): for j in range(7): e = i + j if e > max_e: continue if i < t and j < t: continue U_e_max[e] += 81 return known_sum, U_e_max
def carry_feasible(digits: list[int], lx: list[int], ly: list[int]) -> bool: """ 逐位(真实乘积的低位 -> 高位)DP 判断:是否存在未知对的贡献选择 使得低 L_tail 位可以匹配 dream 尾串 digits。 digits: D[e] (e=0..L_tail-1) """ L_tail = len(digits) if L_tail == 0: return True known_sum, U_e_max = build_known_and_unknown_bounds(lx, ly, L_tail) carry_min = 0 carry_max = 0 for e in range(L_tail): d = digits[e] S_known = known_sum[e] Umax = U_e_max[e] new_carry_min = None new_carry_max = None for carry_in in range(carry_min, carry_max+1): base = S_known + carry_in R = (d - (base % 10)) % 10 if R > Umax: continue k_min = R tmax = (Umax - R)//10 k_max = R + 10*tmax co_min = (base + k_min)//10 co_max = (base + k_max)//10 if new_carry_min is None or co_min < new_carry_min: new_carry_min = co_min if new_carry_max is None or co_max > new_carry_max: new_carry_max = co_max if new_carry_min is None: return False carry_min, carry_max = new_carry_min, new_carry_max return True
solutions = []
def search_mode(all_two_digit: bool, single_pos: int | None, continue_search: bool, show_progress: bool): """ all_two_digit = True : k=7 模式 all_two_digit = False : k=6 模式, single_pos 指定哪一个块(0..6, 低位=0) 为单一位积 single_pos: 块索引(低位=0);只有在 all_two_digit=False 时使用 """ mode_desc = "k=7(all 2-digit)" if all_two_digit else f"k=6(single at block {single_pos})" print(f"[MODE] {mode_desc}")
if all_two_digit: first_candidates = two_digit_pairs else: first_candidates = one_digit_pairs if single_pos == 0 else two_digit_pairs
pbar = tqdm(first_candidates, desc="First block", disable=not show_progress)
def dfs(block_index: int, blocks_low_to_high: list[str], lx: list[int], ly: list[int]): t_blocks = block_index tail_str = "".join(reversed(blocks_low_to_high)) L_tail = len(tail_str)
r = 7 - t_blocks
single_used = 0 if all_two_digit else 1 if not all_two_digit: pass
target_total_len = 15 if all_two_digit else 14 current_total_min = 1 + L_tail + r*1 current_total_max = 1 + L_tail + r*2 if current_total_min > target_total_len or current_total_max < target_total_len: return
if L_tail > 0: digits = list(map(int, reversed(tail_str))) if not carry_feasible(digits, lx, ly): return
if t_blocks == 7: dream_tail = tail_str total_len = 1 + len(dream_tail) if total_len != target_total_len: return if all_two_digit: if any(len(b) != 2 for b in blocks_low_to_high): return else: for idx, blk in enumerate(blocks_low_to_high): if idx == single_pos: if len(blk) != 1: return else: if len(blk) != 2: return
if len(lx) != 7 or len(ly) != 7: return x_digits = [None]*8 y_digits = [None]*7 for i, dval in enumerate(lx): x_digits[7 - i] = dval for i, dval in enumerate(ly): y_digits[6 - i] = dval
blocks_from_high = list(reversed(blocks_low_to_high)) suffix = "".join(blocks_from_high) for x0 in range(1, 10): x_digits[0] = x0 x = int("".join(map(str, x_digits))) y = int("".join(map(str, y_digits))) dream_val = int(str(x0) + suffix) prod = x * y if prod == dream_val and prod != EXCLUDED: key = sha256(str((x, y)).encode()).digest() pt = AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(CT)) try: dec = pt.decode() except UnicodeDecodeError: dec = repr(pt) print(f"[FOUND] mode={mode_desc} x={x} y={y} prod={prod}") print(f" dream={dream_val}") print(" Decrypted:", dec) solutions.append((x, y, prod, dec)) if not continue_search: raise StopIteration return
k = block_index if all_two_digit: cand = two_digit_pairs else: cand = one_digit_pairs if k == single_pos else two_digit_pairs
for a, b, pval, s in cand: new_blocks = blocks_low_to_high + [s] new_lx = lx + [a] new_ly = ly + [b] new_tail_len = L_tail + len(s) new_r = 7 - (t_blocks + 1) min_total = 1 + new_tail_len + new_r*1 max_total = 1 + new_tail_len + new_r*2 if min_total > target_total_len or max_total < target_total_len: continue dfs(block_index + 1, new_blocks, new_lx, new_ly)
try: for (a, b, pval, s) in pbar: dfs(1, [s], [a], [b]) if solutions and not continue_search: break except StopIteration: pass finally: pbar.close()
def solve(continue_search=False, show_progress=True): search_mode(all_two_digit=True, single_pos=None, continue_search=continue_search, show_progress=show_progress) if solutions and not continue_search: return solutions for pos in range(7): if solutions and not continue_search: break search_mode(all_two_digit=False, single_pos=pos, continue_search=continue_search, show_progress=show_progress) return solutions
if __name__ == "__main__": res = solve(continue_search=False, show_progress=True) if not res: print("No solution found in current search configuration.") else: print(f"Total solutions: {len(res)}") for x, y, prod, dec in res: print(f"x={x} y={y} prod={prod} plaintext={dec}")
|