e2W@rmup
import hashlib
import ecdsa
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import *
from secret import flag
def gen():
curve = ecdsa.NIST256p.generator
order = curve.order()
d = randint(1, order-1)
while d.bit_length() != 256:
d = randint(1, order-1)
pubkey = ecdsa.ecdsa.Public_key(curve, curve * d)
privkey = ecdsa.ecdsa.Private_key(pubkey, d)
return pubkey, privkey, d
def nonce_gen(msg, d):
msg_bin = bin(msg)[2:].zfill(256)
d_bin = bin(d)[2:].zfill(256)
nonce = int(msg_bin[:128] + d_bin[:128], 2)
return nonce
def sign(msg, privkey, d):
msg_hash = bytes_to_long(hashlib.sha256(msg).digest())
nonce = nonce_gen(msg_hash, d)
sig = privkey.sign(msg_hash, nonce)
s, r = sig.s, sig.r
return s, r
pk, sk, d = gen()
msg = b'welcome to n1ctf2023!'
s, r = sign(msg, sk, d)
print(f's = {s}')
print(f'r = {r}')
m = pad(flag, 16)
aes = AES.new(long_to_bytes(d), mode=AES.MODE_ECB)
cipher = aes.encrypt(m)
print(f'cipher = {cipher}')
"""
s = 98064531907276862129345013436610988187051831712632166876574510656675679745081
r = 9821122129422509893435671316433203251343263825232865092134497361752993786340
cipher = b'xf3#xffx17xdfxbbxc0xc6vx1bgxc7x8a6xf2xdf~x12xd8]xc5x02Otx99x9fxf7xf3x98xbcx045x08xfbxce1@exbcg[Ixd1xbfxf8xean-'
"""
根据 ECDSA 签名公式
不过这里的
其中 是一整个未知的
签名公式调整为
我们将已知的 放一块,未知的有 ,整理一下就是
其中未知量 是 128 比特的,模数 是 256 比特的,
于是考虑构造格子
使得
本地生成数据测试一下先
import hashlib
import ecdsa
from Crypto.Util.number import *
curve = ecdsa.NIST256p.generator
order = curve.order()
a=ecdsa.NIST256p.curve.a()
b=ecdsa.NIST256p.curve.b()
p=ecdsa.NIST256p.curve.p()
d_ = 112799702410807302994951924898034373856576906910372741339939214376262921112129
s = 109639753269686143977071246486442277683571473955044500468119095108455162948055
r = 115148860739222900018017222670090085973159375004845247957903412786080971680513
msg = b'welcome to n1ctf2023!'
msg_hash = bytes_to_long(hashlib.sha256(msg).digest())
msg_bin = bin(msg_hash)[2:].zfill(256)
M = int(msg_bin[:128], 2) << 128
shift = 2^1024
L = Matrix(ZZ,[[shift*(2^128 * r - s),1,0,0],
[shift*(r),0,1,0],
[shift*(M*s-msg_hash),0,0,2^128],
[shift*(order),0,0,0]])
L=L.LLL()
sage: d_>>128
331488532395847874510754715973804122202
sage: d_%2^128
274665248672883640880810570625320766017
sage: L[1]
(0, -297127138964097507062129757594500328317, -308074196458482485875868587492327724533, 340282366920938463463374607431768211456)
测试发现得到的答案并不是 dh 和 dl, 尝试固定 dh 和 dl 的高位试一试
bruce1 = 0b11<<126
bruce2 = 0b11<<126
shift = 2^1024
tmp = M*s-msg_hash-(2^128 * r - s)*bruce1-r*bruce1
L = Matrix(ZZ,[[shift*(2^128 * r - s),1,0,0],
[shift*(r),0,1,0],
[shift*(tmp),0,0,2^126],
[shift*(order),0,0,0]])
L=L.LLL()
打印结果和 d_ 的高低位对比了一下,也是不太行。尝试换个方式构造格子
由于
于是有
于是考虑构造格子
使得
R = inverse(r,order)
L = Matrix(ZZ,[[R*(2^128 * r - s),1,0],
[R*(M*s-msg_hash),0,2^128],
[(order),0,0]])
L=L.LLL()
sage: L[1]
(308074196458482485875868587492327724533, -297127138964097507062129757594500328317, 340282366920938463463374607431768211456)
还是不太对,,也爆破两个高位试试
R = inverse(r,order)
bruce1 = 0b11<<126
bruce2 = 0b11<<126
tmp = R*(M*s-msg_hash) - bruce1 * R*(2^128 * r - s) - bruce2
L = Matrix(ZZ,[[R*(2^128 * r - s),1,0],
[tmp,0,2^126],
[(order),0,0]])
L=L.LLL()
sage: L[1]
(52862421267778638278337631918501565941, -41915363773393659464598802020674169725, 85070591730234615865843651857942052864)
还是不对,,不知道问题在哪。
但是拿着题目的数据来搞是可以搞出 d 的,emmmmm搞不懂了
import hashlib
import ecdsa
from Crypto.Util.number import *
curve = ecdsa.NIST256p.generator
order = curve.order()
a=ecdsa.NIST256p.curve.a()
b=ecdsa.NIST256p.curve.b()
p=ecdsa.NIST256p.curve.p()
s = 98064531907276862129345013436610988187051831712632166876574510656675679745081
r = 9821122129422509893435671316433203251343263825232865092134497361752993786340
msg = b'welcome to n1ctf2023!'
msg_hash = bytes_to_long(hashlib.sha256(msg).digest())
msg_bin = bin(msg_hash)[2:].zfill(256)
M = int(msg_bin[:128], 2) << 128
R = inverse(r,order)
bruce1 = 0b11<<126
bruce2 = 0b11<<126
tmp = R*(M*s-msg_hash) - bruce1 * R*(2^128 * r - s) - bruce2
L = Matrix(ZZ,[[R*(2^128 * r - s),1,0],
[tmp,0,2^126],
[(order),0,0]])
L=L.LLL()
dh = bruce1-L[0][1]
dl = bruce2+L[0][0]
d=(dh<<128)+dl
from Crypto.Cipher import AES
cipher = b'xf3#xffx17xdfxbbxc0xc6vx1bgxc7x8a6xf2xdf~x12xd8]xc5x02Otx99x9fxf7xf3x98xbcx045x08xfbxce1@exbcg[Ixd1xbfxf8xean-'
aes = AES.new(long_to_bytes(d), mode=AES.MODE_ECB)
m = aes.decrypt(cipher)
print(f'm = {m}')
其实也可以用另一种做法,因为我们已经搞出式子
未知数是 ,可以直接去搞一下二元 copper
import itertools
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
R = f.base_ring()
N = R.cardinality()
f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m+1):
base = N^(m-i) * f^i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1/factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B*monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []
import hashlib
import ecdsa
from Crypto.Util.number import *
curve = ecdsa.NIST256p.generator
order = curve.order()
a=ecdsa.NIST256p.curve.a()
b=ecdsa.NIST256p.curve.b()
p=ecdsa.NIST256p.curve.p()
s = 98064531907276862129345013436610988187051831712632166876574510656675679745081
r = 9821122129422509893435671316433203251343263825232865092134497361752993786340
msg = b'welcome to n1ctf2023!'
msg_hash = bytes_to_long(hashlib.sha256(msg).digest())
msg_bin = bin(msg_hash)[2:].zfill(256)
M = int(msg_bin[:128], 2) << 128
R = inverse_mod(r,order)
bruce1 = 0b11<<126
bruce2 = 0b11<<126
tmp = int(R*(M*s-msg_hash) - bruce1 * R*(2^128 * r - s) - bruce2)
bounds = (2^126, 2^126)
P.<x, y> = PolynomialRing(Zmod(order))
f = tmp - R*(2^128 * r - s)*x - y
res = (small_roots(f, bounds,m=3,d=4))[0]
dh = (bruce1 + res[0]) % order
dl = (bruce2 + res[1]) % order
d = (dh<<128)+dl
sage: d
75767369414377063170504861698029562004628781620076152109208613361284011206099
e20k
#!/usr/bin/sage
from Crypto.Util.Padding import pad
from Crypto.Util.number import *
from Crypto.Cipher import AES
from hashlib import md5
import signal
import random
import os
FLAG = os.environ.get('FLAG', 'n1ctf{XXXXFAKE_FLAGXXXX}')
class ECPrng:
def __init__(self):
self.N = self.keygen()
self.E = EllipticCurve(Zmod(self.N), [3, 7])
self.bias = random.randint(1, 2^128)
self.state = None
print(f"N = {self.N}")
def keygen(self):
P = getPrime(256)
while True:
q = getPrime(256)
if is_prime(2*q-1):
Q = 2*q^2-q
return P*Q
def setState(self, x0, y0):
self.state = self.E(x0, y0)
def next(self):
self.state = 4*self.state
return int(self.state.xy()[0])+self.bias
def v2l(v):
tp = []
for item in v:
tp.append(item.list())
return tp
def Sample(eta, num, signal=0):
if signal:
random.seed(prng.next())
s = []
for _ in range(num):
if random.random() < eta:
s.append(1)
else:
s.append(0)
return Rq(s)
class P:
def __init__(self, A, s, t):
self.A = A
self.s = s
self.t = t
self.y = None
def generateCommit(self, Verifier):
self.y = vector(Rq, [Sample(0.3, N, 1) for _ in range(m)])
w = self.A * self.y
Verifier.w = w
print(f"w = {w.list()}")
def generateProof(self, Verifier, c):
z = self.s*c + self.y
print(f"z = {v2l(z)}")
Verifier.verifyProof(z)
class V:
def __init__(self, A, t):
self.A = A
self.t = t
self.w = None
self.c = None
def challenge(self, Prover):
self.c = Sample(0.3, N)
print(f"c = {self.c.list()}")
Prover.generateProof(self, self.c)
def verifyProof(self, z):
if self.A*z == self.t*self.c + self.w:
return True
return False
def Protocol(A, secret, t):
prover = P(A, secret, t)
verifier = V(A, t)
prover.generateCommit(verifier)
verifier.challenge(prover)
if __name__ == "__main__":
try:
signal.alarm(120)
print("ECPRNG init ...")
prng = ECPrng()
x0, y0 = input("PLZ SET PRNG SEED > ").split(',')
prng.setState(int(x0), int(y0))
N, q, m = (256, 4197821, 15)
PRq.<a> = PolynomialRing(Zmod(q))
Rq = PRq.quotient(a^N + 1, 'x')
A = vector(Rq, [Rq.random_element() for _ in range(m)])
secret = vector(Rq, [Sample(0.3, N) for _ in range(m)])
t = A*secret
print(f"A = {v2l(A)}")
print(f"t = {t.list()}")
Protocol(A, secret, t)
cipher = AES.new(md5(str(secret).encode()).digest(), mode=AES.MODE_ECB)
print(f"ct = {cipher.encrypt(pad(FLAG.encode(), 16)).hex()}")
except:
print("error!")
这个代码比较多,我们从后面往前看,题目最终用 secret 作为 AES 的 key 加密了 FLAG,于是这道题就是需要我们利用信息求出 secret,其中 secret = vector(Rq, [Sample(0.3, N) for _ in range(m)])
,打印一下这个 secret 是一个含有 15 个随即多项式的数组,很麻
看到它是由 Sample 函数生成的,
def Sample(eta, num, signal=0):
if signal:
random.seed(prng.next())
s = []
for _ in range(num):
if random.random() < eta:
s.append(1)
else:
s.append(0)
return Rq(s)
Sample 函数调用了 random.seed(prng.next())
,种子由 prng
也就是 ECPrng()
决定。
class ECPrng:
def __init__(self):
self.N = self.keygen()
self.E = EllipticCurve(Zmod(self.N), [3, 7])
self.bias = random.randint(1, 2^128)
self.state = None
print(f"N = {self.N}")
def keygen(self):
P = getPrime(256)
while True:
q = getPrime(256)
if is_prime(2*q-1):
Q = 2*q^2-q
return P*Q
def setState(self, x0, y0):
self.state = self.E(x0, y0)
def next(self):
self.state = 4*self.state
return int(self.state.xy()[0])+self.bias
ECPrng
每次状态更新函数是计算 self.state = 4*self.state
,并且我们可以控制第一个 state: x0, y0 = input("PLZ SET PRNG SEED > ").split(',')
因此如果我们能够在这个曲线上找到一个阶为 3 的点,那么这个状态就永远不会变了。(虽然目前也还不知道有啥用)
另外题目给出的数据是 v2l(A)
和 t = A*secret
,A 和 secret 都是有 15 个多项式元素的向量, t 是它们的内积,如果 secret 的每个元素都一样(前面我们控制state的操作有意义了),那么就有 ,于是只需要根据 v2l(A)
恢复 A 就行。
def v2l(v):
tp = []
for item in v:
tp.append(item.list())
return tp
v2l 只是一个向量转list的函数啊,emmm,那这题好像就这么轻易的解决啦?只需要找到一个 3-torsion
class ECPrng:
def __init__(self):
self.N = self.keygen()
self.E = EllipticCurve(Zmod(self.N), [3, 7])
self.bias = random.randint(1, 2^128)
self.state = None
print(f"N = {self.N}")
def keygen(self):
P = getPrime(256)
while True:
q = getPrime(256)
if is_prime(2*q-1):
Q = 2*q^2-q
return P*Q
这里曲线的模数不是素数,所以我们还得先分解一下。
设 为素数,于是 ,那么
因此 是 的一个倍数,回想一下 William’s p+1攻击 的条件,对于 ,如果能够找到 的倍数 ,那么,那么就可以通过计算 计算出 的分解 ,其中 是 Lucas 序列,那么这里,我们就有
# https://tover.xyz/p/2023-N1CTF-Crypto-Part/#e20k
def V(k, N, A=5):
M = matrix(Zmod(N), [[A, -1],[1, 0]])
v = vector(Zmod(N), [A, 2])
vk = M^k * v
return Integer(vk[1])
def factorN(N):
A = 5
while True:
v2n = V(2*N, N, A)
g = gcd(v2n-2, N)
print('[Log] %d - %d' % (A, g))
if g.nbits() >= 256 and is_prime(g):
r = g
q = (r+1) // 2
p = N // (q * r)
if p * q * r == N:
return p, q, r
A = next_prime(A)
至于找 3 – torsion,就先分别在子群 下找到 3 – torsion,然后再 crt 一下应该就是模 n 下的 3 – torsion 了叭(虽然我暂时并不能给出证明,但直觉是这样的)。不过并不是所有曲线都一定会有 3 – torsion 的,所以还是要多交互几次,多试几个 N(大概十个里面有一个可以)。
# https://tover.xyz/p/2023-N1CTF-Crypto-Part/#e20k
def check_order_k(fN, k):
p, q, r = fN
Ep = EllipticCurve(GF(p), [3, 7])
Eq = EllipticCurve(GF(q), [3, 7])
Er = EllipticCurve(GF(r), [3, 7])
for Ex in (Ep, Eq, Er):
if Ex.order() % k != 0:
return False
return True
def EN_random_element_k(fN, k=3):
p, q, r = fN
Ep = EllipticCurve(GF(p), [3, 7])
Eq = EllipticCurve(GF(q), [3, 7])
Er = EllipticCurve(GF(r), [3, 7])
Ps = [(Ex.order() // k) * Ex.random_element() for Ex in (Ep, Eq, Er)]
x = crt([Integer(Px.xy()[0]) for Px in Ps], [p, q, r])
y = crt([Integer(Px.xy()[1]) for Px in Ps], [p, q, r])
En = EllipticCurve(Zmod(N), [3, 7])
assert En(x,y)*4 == En(x,y)
return x, y
def keygen():
P = getPrime(256)
while True:
q = getPrime(256)
if is_prime(2*q-1):
Q = 2*q^2-q
return P*Q
while True:
N = keygen()
fN = factorN(N)
if check_order_k(fN,3):
print(N)
x,y = EN_random_element_k(fN, k=3)
print(x,y)
break
#1013875582627032602677626533185964955011127677196354420434828713957246193599624662633588734228349095074683816857070250517758699617638835360531234909866250719846403738306106812969931362411442466901415440311143618623850290712922404891
#388270114163740316018563212433699905540435323654437709674213902800454865413107430369939099091487225499733029234058897062202031722366681230348387684468253806971170013436257172428790862397692286579969065262734353479934623815448967554 39264094293498434137845233422870293264878580043367647949186920341399958082223132643327768740861704251226249769008326413513274274898546333080951020122589704432717184022958189792274187727413181175120896520213476497528219270487500650
本以为这题到此为止了,只需要再简单的计算一下 就可以了,测试时才发现
def Sample(eta, num, signal=0):
if signal:
random.seed(prng.next())
...
def generateCommit(self, Verifier):
self.y = vector(Rq, [Sample(0.3, N, 1) for _ in range(m)])
w = self.A * self.y
Verifier.w = w
print(f"w = {w.list()}")
secret = vector(Rq, [Sample(0.3, N) for _ in range(m)])
只有当 Sample 函数的 signal=1 的时候才触发种子的更新,因此我们只能控制 的每一个元素相同。
于是有
题目还给出了 z = self.s*c + self.y
因此我们还能计算
从而有 (小绕了一下,问题不大~)
PRq.<a> = PolynomialRing(Zmod(q))
Rq = PRq.quotient(a^N + 1, 'x')
A = vector(Rq, [Rq(i) for i in A])
t = Rq(t)
w = Rq(w)
c = Rq(c)
z = vector(Rq, [Rq(i) for i in z])
y = w/sum(A)
y = vector(Rq, [y]*15)
res = z - y
secret=[]
for i in range(15):
secret.append(res[i]/c)
secret = vector(Rq,secret)
e2D1p
from Crypto.Util.number import *
import os
FLAG = os.environ.get('FLAG', b'n1ctf{XXXXFAKE_FLAGXXXX}')
assert FLAG[:6] == b'n1ctf{' and FLAG[-1:] == b'}'
FLAG = FLAG[6:-1]
def keygen(nbits):
while True:
q = getPrime(nbits)
if isPrime(2*q+1):
if pow(0x10001, q, 2*q+1) == 1:
return 2*q+1
def encrypt(key, message, mask):
return pow(0x10001, message^mask, key)
p = keygen(512)
flag = bytes_to_long(FLAG)
messages = [getRandomNBitInteger(flag.bit_length()) for i in range(200)]
enc = [encrypt(p, message, flag) for message in messages]
print(f'message = {messages}')
print(f'enc = {enc}')
题目生成了 200 个随机消息 message
,并给出了对应密文,加密方式为 ,设 ,简记为 ,不过 未知
看起来像是根据 和 解出幂次 的 dlp 问题,但是这里并不知道 p,并且 也并不光滑,应该是要利用 200 条 message
构造出些什么。
首先第一步肯定还是要想办法把这个 给恢复出来的,我们手里的密文 是与 p 相关的。
根据异或的性质,我们可以这样表示
于是
如果我们能够找到比较小的 使得
,再把这些 分为正负两部分 ,就有 。那么多找几个这样的 就能通过 gcd 求出 p 了。
将 展开,得到
两边取对数
因此我们只需要使得 和 即可
我们构造如下矩阵,
其中 是一个放大系数,为了使得目标向量对应元素为 0 。
# https://github.com/Nu1LCTF/n1ctf-2023/blob/main/crypto/e2D1p/solution/sol.sage
m_l = 159
dim = 200
L = matrix(ZZ, dim, dim+m_l+1)
g = 2^512
for i in range(dim):
L[i, i] = 1
L[i, dim+m_l] = g
for i in range(dim):
line = bin(message[i])[2:].rjust(m_l, '0')
for j in range(len(line)):
if int(line[j]):
L[i, dim+j] = g
else:
L[i, dim+j] = 0
basis = L.LLL()[:40]
p = []
for item in basis:
g1 = 1
g2 = 1
vec = item[:dim]
for i in range(len(vec)):
if vec[i] >= 0:
g1 *= pow(enc[i], vec[i])
else:
g2 *= pow(enc[i], -vec[i])
p.append(g1-g2)
p = list(factor(reduce(GCD, p)))[-1][0]
# 25070940894294854944213724808057610995878580501834029791436842569011975159898869595617649716256078890953233822913946718277574163417715224718477846001735447
有了 之后回到式子
构造矩阵
尝试解 ,发现无解,即意味着 时,
无解
解 ,发现有解,即意味着 时,
有解
再尝试解 ,即意味着求
设 ,如果 ,则说明 ,如果 ,则说明
# https://blog.maple3142.net/2023/10/23/n1ctf-2023-writeups/#e2d1p
from sage.all import *
from Crypto.Util.number import *
from subprocess import check_output
from re import findall
from binteger import Bin
# ref: https://affine.group/writeup/2022-01-TetCTF#fault
bl = 159
mat = matrix(
ZZ,
[
Bin(e, bl).tuple[::-1] + (1,) for e, c in zip(message, enc)
], # 1 for the base b = 0x10001^flag
)
def field_exp(el, e):
a, b = e.numerator(), e.denominator()
el **= a
q = el.parent().characteristic() // 2
d = inverse_mod(b, q)
return el**d
p = 25070940894294854944213724808057610995878580501834029791436842569011975159898869595617649716256078890953233822913946718277574163417715224718477846001735447
F = GF(p)
sol = mat.solve_left(vector([0] * (bl - 1) + [1, 1])) # pick a base that works
y = product([field_exp(F(y), z) for y, z in zip(enc, sol)])
recovered_bits = [0] * bl
recovered_bits[bl - 1] = 1
for i in reversed(range(bl - 1)):
base = vector([0] * (bl - 1) + [1, 1])
base[i] = 1
sol = mat.solve_left(base)
x = product([field_exp(F(y), z) for y, z in zip(enc, sol)])
z = F(0x10001) ** (2**i)
if x == y * z:
recovered_bits[i] = 0
else:
recovered_bits[i] = 1
flag = Bin(recovered_bits[::-1]).bytes
print(flag)
# n1ctf{s0o0_ezzz_dLLLp%$#@!}
e2ls0
from string import ascii_letters
from sympy import sqrt
import random
import signal
import os
FLAG = os.environ.get('FLAG', 'n1ctf{XXXXFAKE_FLAGXXXX}')
def banner():
print("""
░░░░░░░ ░░░░░░ ░░ ░░░░░░░ ░░░░░░
▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒
▒▒▒▒▒ ▒▒▒▒▒ ▒▒ ▒▒▒▒▒▒▒ ▒▒ ▒▒
▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓
███████ ███████ ██ ███████ ██████
""")
def curve_init():
p = random_prime(2^256)
F.<i> = GF(p^2, modulus = x**2 + 1)
R.<t> = PolynomialRing(GF(p))
guess = ''.join(random.choices(ascii_letters, k=20))
RR = RealField(256)
num = RR(int(guess.encode().hex(),16))
j = F(str(sqrt(num)).split('.')[1])
E = EllipticCurve(j=j)
P = E(0).division_points(3)
P.remove(E(0))
phi = E.isogeny(random.choice(P))
E2 = phi.codomain()
j2 = E2.j_invariant()
assert list(R(j2))[1] != 0
return E2, p, guess
def leak(E, p):
F = Zmod(p^2)
R.<t> = PolynomialRing(GF(p))
r = random.getrandbits(20)
x = F(input("your magic number?n$ "))^r - 1
j_ = E.j_invariant()^x
print(list(R(j_))[0])
def main():
signal.alarm(120)
banner()
para = None
print("Curve Initialization...")
while not para:
try:
para = curve_init()
except:
continue
E, p, guess = para
print(f"einfo: {E.base_ring()}")
leak(E, p)
if input("guess > ").strip('n') == guess:
print(f"Congratz, your flag: {FLAG}")
else:
print("Game over!")
if __name__ == "__main__":
try:
main()
except:
print("error!")
椭圆曲线的同源,仍然在知识盲区,这个还得去学一下才能做,放入 todo list 先 ~
原文始发于微信公众号(Van1sh):2023 N1CTF