照烟照烟
Relax

神秘钓鱼题与1999

神秘钓鱼题

刷 b 站刷到了这个:

看似很简单啊,实则暗藏玄 🐔

看评论区说好像和椭圆曲线有关,于是就一时兴起,查了很多资料,写一下这道题的 wp(

首先把可爱的水果换成熟悉的 a,b,c ,得到:

ab+c+ba+c+ca+b=4

两边同乘以三项分母的积:

a3+b3+c33(a2b+ab2+a2c+ac2+b2c+bc2)5abc=0

在射影平面 P2 上,这个方程定义了一条亏格为 1 的三次曲线。根据代数几何理论,任何具有有理点的亏格为 1 的光滑代数曲线都同构于一条魏尔斯特拉斯形式的椭圆曲线。而为什么要把这个问题放在椭圆曲线上讨论呢?因为在属于非线性丢番图的原方程下,虽然我们有计算速度极快的计算机,但是遍历求解极其困难(因为这玩意满足条件的最小解集每个解也有 80 位数),时间复杂度是指数级的。转化到椭圆曲线上使用点加法遍历,时间复杂度仅仅是对数级(得益于椭圆曲线的先天优势)。既然可以映射,那就放手去做吧!

要想把这一堆玩意映射到椭圆曲线上,首先要找到椭圆曲线点加法群上的单位元(一般是无穷远点)。这个点只要有理就行,无关正负。所以我们注意到(这次是真的注意到好吧

a=4,b=1,c=11

是满足 ab+c+ba+c+ca+b4=0 的一组解。

把变换后的方程喂给 sagemath,附赠一组基点,sagemath 就能自动把它们映射到椭圆曲线上,还能自己映射回来,代码如下:

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
# 定义多元多项式环
R.<a, b, c> = QQ[]
cubic = a*(a+c)*(a+b) + b*(b+c)*(a+b) + c*(b+c)*(a+c) - 4*(b+c)*(a+c)*(a+b)
# 让 sage 自己计算双有理映射,这里得到的不是真正的椭圆曲线,而是态射(Morphism)得到的独立对象
E_iso = EllipticCurve_from_cubic(cubic, [4, -1, 11], morphism=True)
# 提取目标曲线,其中 codomain()方法在这里是取映射完成的椭圆曲线
E = E_iso.codomain()
# 逆向映射函数
inv = E_iso.inverse()
# 基点
P = E.gens()[0]
# 定义初始倍加次数
k = 1

while True:
# 遍历 k,若倍加得到的点是无穷原点,跳过本次循环
cp = k*P
if cp.is_zero():
k += 1
continue
# 先把 abc 映射回去,便于判断。此时的'abc'是一个列表
abc = inv(cp)
a_frac = abc[0]
b_frac = abc[1]
c_frac = abc[2]
# 判断是否均为正整数(全是负的也行,也是同号)
if (a_frac > 0 and b_frac > 0 and c_frac > 0) or (a_frac < 0 and b_frac < 0 and c_frac < 0):
a_val = abs(a_frac)
b_val = abs(b_frac)
c_val = abs(c_frac)
# 将 abc 化为整数,.denominator()属性取分数的分母
abc_lcm = lcm([a_val.denominator(), b_val.denominator(), c_val.denominator()])
# 通分,.numerator()属性取分数的分子
a_int = a_val.numerator() * (abc_lcm // a_val.denominator())
b_int = b_val.numerator() * (abc_lcm // b_val.denominator())
c_int = c_val.numerator() * (abc_lcm // c_val.denominator())
# 除以最大公约数
g = gcd(a_int, gcd(b_int, c_int))
a = a_int // g
b = b_int // g
c = c_int // g

print(f"k = {k}")
print(f"a = {a}")
print(f"b = {b}")
print(f"c = {c}")
# 记得退出循环,避免卡死
break
# 记得让 k 自增
k += 1

也是得到结果:

1
2
3
4
k = 9
a = 154476802108746166441951315019919837485664325669565431700026634898253202035277999
b = 4373612677928697257861252602371390152816537558161613618621437993378423467772036
c = 36875131794129999827197811565225474825492979968971970996283137471637224634055579

真是简简又单单啊(bushi

1999

最近哑谜的活动,深蓝在刻录机界面埋了个彩蛋,是两个字符串:

1
2
6861766566756E
060E020D0F1B09000404005C5C

这种解密的话,第一个字符串是 ASCII 码映射,直接解码就行。

第二个字符串猜测是异或得到(因为超出了 ASCII 码表值,而且常见的加密方式也就这个了)

应该是拿第一个字符串循环和第二个字符串逐字节异或。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
from itertools import cycle
a = "6861766566756E"
b = "060E020D0F1B09000404005C5C"
ls_a = [a[i : i + 2] for i in range(0, len(a), 2)]
ls_b = [b[j : j + 2] for j in range(0, len(b), 2)]
zipped = zip(cycle(ls_a), ls_b)
ls = []
for a, b in zipped:
ls.append(int(a, 16) ^ int(b, 16))
result = "".join(chr(k) for k in ls)
print(result)

得到

1
2
havefun
nothinghere:)

还真是密码学专题,震撼美味 QwQ