Skip to main content

Crypto CTF 2025

·13 mins· loading · loading · ·
hokak
Author
hokak
need more sleep
Table of Contents

team:https://github.com/killua4564/2025-Crypto-CTF latex 好像有點壞掉,可以看: https://hackmd.io/@hokak/CryptoCTF2025

vinad
#

source

#!/usr/bin/env python3

from Crypto.Util.number import *
from flag import flag

def parinad(n):
	return bin(n)[2:].count('1') % 2

def vinad(x, R):
	return int(''.join(str(parinad(x ^ r)) for r in R), 2)

def genkey(nbit):
	while True:
		R = [getRandomNBitInteger(nbit) for _ in range(nbit)]
		r = getRandomNBitInteger(nbit)
		p, q = vinad(r, R), getPrime(nbit)
		if isPrime(p):
			e = vinad(r + 0x10001, R)
			if GCD(e, (p - 1) * (q - 1)) == 1:
				return (e, R, p * q), (p, q)

def encrypt(message, pubkey):
	e, R, n = pubkey
	return pow(message + sum(R), e, n)

nbit = 512
pubkey, _ = genkey(nbit)
m = bytes_to_long(flag)
assert m < pubkey[2]
c = encrypt(m, pubkey)

print(f'R = {pubkey[1]}')
print(f'n = {pubkey[2]}')
print(f'c = {c}')

弱點在

R = [getRandomNBitInteger(nbit) for _ in range(nbit)]
r = getRandomNBitInteger(nbit)
p, q = vinad(r, R), getPrime(nbit)

p是用r和R去生成的,但沒公開r,不過前面R使用了512次512的randombit生成
所以可以用Randcrack來預測r

R = [...略]
n = 58113574203067314600162910771848744432179168354040678920098167335472534222998261639291145191568159464990603689062679467360303185717662426122140998218656632568172511390111887830539687208220100574329903748617343193392646019854280519859403817579746765861359633174218846216669659258251676438195667516224684805919
c = 56754194307199340085459028397027924827853574000671575387226403396873568994756738512141122143372650573201079937375922460851170745485734799044781029943783218210457587599666501326645229924138230588050782907693019958930006807017898115655426823272342984109999519420817119999272583495848119171867835187241510764427

import random, time
from randcrack import RandCrack
from Crypto.Util.number import *

def parinad(n):
    return bin(n)[2:].count('1') % 2

def vinad(x, R):
    return int(''.join(str(parinad(x ^ r)) for r in R), 2)

rc = RandCrack()

cnt = 0
for x in R[-39:]:
    for i in range(16):
        rc.submit(x % (1 << 32))
        x >>= 32

r = rc.predict_getrandbits(512)
p = vinad(r, R)
q = n // p
e = vinad(r + 0x10001, R)
d = pow(e, -1, (p-1)*(q-1))

print(long_to_bytes(pow(c, d, n) - sum(R)).decode())
# CCTF{s0lV1n9_4_Syst3m_0f_L1n3Ar_3qUaTi0n5_0vEr_7H3_F!3lD_F(2)!}

tofee
#

source

#!/usr/bin/env sage

import sys
from Crypto.Util.number import *
from hashlib import sha512
flag = b'test_flag'

def die(*args):
	pr(*args)
	quit()
	
def pr(*args):
	s = " ".join(map(str, args))
	sys.stdout.write(s + "\n")
	sys.stdout.flush()
	
def sc():
	return sys.stdin.buffer.readline()

def sign(msg, skey):
    global k

    h = bytes_to_long(sha512(msg).digest())
    k = toffee(u, v, k)
    print(f'{k = }')
    print(int(k).bit_length())
    P = k * G
    r = int(P.xy()[0]) % _n
    s = inverse(k, _n) * (h + r * skey) % _n
    return (r, s)

def toffee(u, v, k):
	return (u * k + v) % _n

def main():
    border = "┃"
    pr(        "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
    pr(border, ".:::    Welcome to the Toffee chocolate cryptography task!    ::.", border)
    pr(border, ".:  Your mission is to find flag by analyzing the signatures!  :.", border)
    pr(        "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
    global flag, u, v, k, _n, G
    skey = bytes_to_long(flag)
    p = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41882ebea6f6e7b0e959d2c36ba5e27705daffacd9a49b39d5beedc74976b30a260c9
    a, b = -7, 0xd3f1356a42265cb4aec98a80b713fb724f44e747fe73d907bdc598557e0d96c5
    _n = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41881d942f0dddae61b0641e2a2cf144534c42bf8a9c3cb7bdc2a4392fcb2cc01ef87
    x = 0xa0e29c8968e02582d98219ce07dd043270b27e06568cb309131701b3b61c5c374d0dda5ad341baa9d533c17c8a8227df3f7e613447f01e17abbc2645fe5465b0
    y = 0x5ee57d33874773dd18f22f9a81b615976a9687222c392801ed9ad96aa6ed364e973edda16c6a3b64760ca74390bb44088bf7156595f5b39bfee3c5cef31c45e1
    F = FiniteField(p)
    E = EllipticCurve(F, [a, b])
    G = E(x, y)
    u, v, k = [randint(1, _n) for _ in ';-)']
    print(f"test: {u, v, k}")
    while True:
        pr(f"{border} Options: \n{border}\t[G]et toffee! \n{border}\t[S]ign message! \n{border}\t[Q]uit")
        ans = sc().decode().strip().lower()
        if ans == 'g':
            pr(border, f'Please let me know your seed: ')
            _k = sc().decode().strip()
            try:
                _k = int(_k)
            except:
                die(border, 'Your seed is not valid! Bye!!')
            pr(f'{toffee(u, v, _k) = }')
        elif ans == 's':
            pr(border, f'Please send your message: ')
            msg = sc().strip()
            print(msg)
            r, s = sign(msg, skey)
            
            pr(border, f'{r = }')
            pr(border, f'{s = }')
        elif ans == 'q':
            die(border, "Quitting...")
        else:
            die(border, "Bye...")

if __name__ == '__main__':
	main()

題目要我們recover私鑰skey 而這個簽章的k生成是使用LCG,且u和v可以透過[G]et toffee!獲得\

def toffee(u, v, k):
	return (u * k + v) % _n

然後這題我有在AIS3 pre-exam 2025打過,所以後面我就搬之前的

$k = random$ $k_2 = uk + v$

我們知道 $s = k^{-1}(h+dr) \pmod n$ 所以這邊可以假設收到的前組r、s有這樣的關係,而k並不是完全隨機

\begin{align} k = s_1^{-1}h + s_1^{-1}dr_1 \pmod n\ uk + v = s_2^{-1}h + s_2^{-1}dr_2 \pmod n \end{align}

因為$a、b、r_1、r_2、s_1、s_2、h、n$已知 所以就變成一個聯立方程式,未知數為$k、d$ 為了整理係數,我們把兩組方程式移項:

\begin{align} k - s_1^{-1}dr_1 - s_1^{-1}h = 0 \pmod n\ uk - s_2^{-1}dr_2 - s_2^{-1}h + v = 0 \pmod n \end{align}

from pwn import *
from Crypto.Util.number import *
from sage.all import *
from hashlib import sha512

border = "┃"
options = f"{border} Options: \n{border}\t[G]et toffee! \n{border}\t[S]ign message! \n{border}\t[Q]uit"

io = remote('91.107.188.9', 31111)

def toffee(seed):
    io.sendlineafter(options, b'g')
    io.sendlineafter('Please let me know your seed: ', str(seed).encode())
    io.recvuntil(b'toffee(u, v, _k) = ')
    return int(io.recvline().strip().decode())
# (u * k + v) % _n


n = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41881d942f0dddae61b0641e2a2cf144534c42bf8a9c3cb7bdc2a4392fcb2cc01ef87

v = toffee(0)
u = (toffee(1) - v) % n
print(u, v)
assert (10000 * u + v) % n == toffee(10000)

F = GF(n)
R = PolynomialRing(F, names=('k', 'd'))  
k, d = R.gens()

message_hash = bytes_to_long(sha512(b'a').digest())
sigs = []
for i in range(2):
    io.sendlineafter(options, b's')
    io.sendlineafter(f'Please send your message: ', b'a')
    io.recvuntil(b'r = ')
    r = int(io.recvline().strip())
    io.recvuntil(b's = ')
    s = int(io.recvline().strip())
    sigs.append([message_hash, r, s])


def rng(x, y):
    for _ in range(y):
        x = (x*u + v)
    return x

P = []
for i, sig in enumerate(sigs):
    h, r, s = sig
    inv_s = pow(s, -1, n)
    P.append(inv_s * h + inv_s * d * r - rng(k, i))

M = Matrix(F, [
    [Pp.coefficient({k:1, d:0}), Pp.coefficient({k:0, d:1})]
    for Pp in P
])

V = Matrix(F, len(P), 1, [-Pp.coefficient({k:0, d:0}) for Pp in P])

sol = M.solve_right(V)
k = Integer(sol[0][0])
d = Integer(sol[1][0])

print(long_to_bytes(int(d)))
# CCTF{4fFin3Ly_r3lA7eD_n0nCE5_aR3_!n5eCuR3!}

sobata
#

#!/usr/bin/env sage

import sys
from Crypto.Util.number import *
from flag import FLAG

def die(*args):
    pr(*args)
    quit()

def pr(*args):
    s = " ".join(map(str, args))
    sys.stdout.write(s + "\n")
    sys.stdout.flush()

def sc(): 
    return sys.stdin.buffer.readline()

def gen_params(nbit):
    while True:
        p = getPrime(nbit)
        if p % 6 == 1:
            F = GF(p)
            R = [F.random_element() for _ in '01']
            a, b = [R[_] ** ((p - 1) // (3 - _)) for _ in [0, 1]]
            if a != 1 and b != 1:
                c, d = [F.random_element() for _ in '01']
                E = EllipticCurve(GF(p), [0, d])
                return (p, E, a, b, c)

def walk(P, parameters):
    p, E, a, b, c = parameters
    x, y = P.xy()
    Q = (a * x, b * y)
    assert Q in E
    return int(c) * E(Q)

def jump(P, n, parameters):
    _parameters = list(parameters)
    _parameters[-1] = pow(int(_parameters[-1]), n, _parameters[1].order())
    return walk(P, _parameters)

def main():
    border = "┃"
    pr(        "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
    pr(border, ".::               Welcome to the Sobata challenge!            ::. ", border)
    pr(border, " You should analyze this weird oracle and break it to get the flag", border)
    pr(        "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
    nbit = 512
    parameters = gen_params(nbit)
    E = parameters[1]
    m = bytes_to_long(FLAG)
    assert m < parameters[0]
    while True:
        try:
            P = E.lift_x(m)
            break
        except:
            m += 1
    while True:
        pr("| Options: \n|\t[E]ncrypted FLAG \n|\t[W]alking with P \n|\t[J]umping over P \n|\t[Q]uit")
        ans = sc().decode().strip().lower()
        if ans == 'e':
            _P = walk(P, parameters)
            pr(border, f'The encrypted flag is: {_P.xy()}')
        elif ans == 'w':
            pr(border, 'Please send your desired point over E: ')
            Q = sc().decode().strip().split(',')
            try:
                Q = [int(_) for _ in Q]
            except:
                die(border, 'Your input is not valid!!')
            if Q in E:
                pr(border, f'The result of the walk is: {walk(E(Q), parameters).xy()}')
            else:
                die(border, 'Your point is not on the curve E! Bye!!')
        elif ans == 'j':
            pr(border, 'Send your desired point over E: ')
            Q = sc().decode().strip().split(',')
            pr(border, 'Let me know how many times you would like to jump over the given point: ')
            n = sc().decode().strip()
            try:
                Q = [int(_) for _ in Q]
                n = int(n)
            except:
                die(border, 'Your input is not valid!!')
            if Q in E:
                pr(border, f'The result of the jump is: {jump(E(Q), n, parameters).xy()}')
            else:
                die(border, 'Your point is not on the curve E! Bye!!')
        elif ans == 'q': die(border, "Quitting...")
        else: die(border, "Bye...")

if __name__ == '__main__':
    main()

這題要我們拿到生成元P的x值

def walk(P, parameters):
    p, E, a, b, c = parameters
    x, y = P.xy()
    Q = (a * x, b * y)
    assert Q in E
    return int(c) * E(Q)

def jump(P, n, parameters):
    _parameters = list(parameters)
    _parameters[-1] = pow(int(_parameters[-1]), n, _parameters[1].order())
    return walk(P, _parameters)

jump 0的話$c$的值會是1
也就是說我們可以拿到$ax$和$by$
而$ax, by$也會再曲線上
但會需要一點在曲線上的,我可以拿加密(walk)過後的G
接下來拿$ax, ax^2, ax^3…ax^n\pmod p$

$ax \cdot ax^3-ax^2\cdot ax^2$
$ax^2 \cdot ax^4-ax^3\cdot ax^3$
$\vdots$
$ax^{n-1}\cdot ax^{n+1}-ax^n \cdot ax^n$
取GCD就可以得到$p$,後面就可以拿$a,b$和$d(y^2 - x^3 \equiv d \pmod p)$
拿到這些曲線的參數之後可以嘗試去jump $\varphi(n) -1,n為曲線E的order$
得到$c^{\varphi(n)-1}\equiv c^{-1}\pmod n$
$\varphi(n)$的獲得難度取決於$n$的分解難度,不過基本上多送幾次就會有比較好分解的數了

這題有更簡單的方法,不過我就不論述了killua大師應該會附

# %%
from pwn import *
from Crypto.Util.number import *
from sage.all import *
io = remote('91.107.133.165', 11177)

def str2point(s) -> tuple:
    l = [int(x) for x in s.strip()[1:-1].decode().split(',')]
    return tuple(l)

def jump(P, n):
    io.sendlineafter(options, b'j')
    io.sendlineafter(b'Send your desired point over E: ', str(P)[1:-1].encode())
    io.sendlineafter(b'Let me know how many times you would like to jump over the given point: ', str(n).encode())
    io.recvuntil(b'The result of the jump is: ')
    return str2point(io.recvline())

def walk(P):
    io.sendlineafter(options, b'w')
    io.sendlineafter(b'Please send your desired point over E: ', str(P)[1:-1].encode())
    io.recvuntil(b'The result of the walk is: ')
    return str2point(io.recvline())


options = b"| Options: \n|\t[E]ncrypted FLAG \n|\t[W]alking with P \n|\t[J]umping over P \n|\t[Q]uit"

io.sendlineafter(options, b'e')
io.recvuntil(b'The encrypted flag is: ')
F = str2point(io.recvline())

def get_params():
    jumped = []
    X = F
    for i in range(8):
        X = jump(X, 0)
        jumped.append(X)

    for i in range(1, 7):
        S = []
        aa = jumped[i][0]**2 - jumped[i - 1][0] * jumped[i + 1][0]
        bb = jumped[i][1]**2 - jumped[i - 1][1] * jumped[i + 1][1]
        S.append(abs(aa))
        S.append(abs(bb))
    
    p = GCD(S)
    a = pow(F[0], -1, p) * jumped[0][0] % p
    b = pow(F[1], -1, p) * jumped[0][1] % p
    d = (F[1] ** 2 - F[0] ** 3) % p
    
    assert F[0] * a % p == jumped[0][0]
    assert F[1] * b % p == jumped[0][1]
    return p, a, b, d

p, a, b, d = get_params()
E = EllipticCurve(GF(p), [0, d])
assert F in E
F = E(F)
n = E.order()
print(n)


# %%
phi = euler_phi(n)
print(phi)

# %%


P = E(F)
F_inv = (pow(a, -1, p) * P[0] % p, pow(b, -1, p) * P[1] % p)
assert F_inv[0] * a % p == P[0]
assert F_inv[1] * b % p == P[1]

print(P.xy())
Q = jump(F_inv, phi-1)
G = (pow(a, -1, p) * Q[0] % p, pow(b, -1, p) * Q[1] % p)
print(long_to_bytes(int(G[0])))
# CCTF{L1n3Ari7y_iN_w4lkIn9_ECC!}

sobata II
#

#!/usr/bin/env sage

import sys, re
from Crypto.Util.number import *
from flag import FLAG

def die(*args):
    pr(*args)
    quit()

def pr(*args):
    s = " ".join(map(str, args))
    sys.stdout.write(s + "\n")
    sys.stdout.flush()

def sc(): 
    return sys.stdin.buffer.readline()

def sanitize_string(inp):
    pattern = r'[^0-9g*+,]|[a-fh-zA-FH-Z]'
    return re.sub(pattern, '', inp)

def gen_params(nbit):
    while True:
        p = getPrime(nbit)
        R.<x> = PolynomialRing(GF(p))
        f = x^2 + 13 * x + 37
        f = R(f)
        if f.is_irreducible():
            F.<g> = GF(p^2, modulus = f)
            while True:
                a, b = [__ ** (__.multiplicative_order() // (3 - _)) for _, __ in enumerate(F.random_element() for _ in ':)')]
                if a.multiplicative_order() - 3 == b.multiplicative_order() - 2 == 0:
                    c, d = [randint(1, p) for _ in ':)']
                    E = EllipticCurve(F, [0, d])
                    return (p, F, E, a, b, c)

def walk(P, parameters):
    p, F, E, a, b, c = parameters
    x, y = P.xy()
    Q = (a * x, b * y)
    assert Q in E
    return int(c) * E(Q)

def jump(P, n, parameters):
    _parameters = list(parameters)
    _parameters[-1] = pow(int(_parameters[-1]), n, _parameters[1].order())
    return walk(P, _parameters)

def main():
    border = "┃"
    pr(        "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
    pr(border, ".::             Welcome to the Sobata II challenge!           ::. ", border)
    pr(border, " You should analyze this weird oracle and break it to get the flag", border)
    pr(        "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
    nbit = 196
    parameters = gen_params(nbit)
    p, F, E = parameters[0], parameters[1], parameters[2]
    g = F.gen()
    m = bytes_to_long(FLAG)
    assert m < p
    while True:
        try:
            P = E.lift_x(m + 1404 * g)
            break
        except:
            m += 1
    while True:
        pr("| Options: \n|\t[E]ncrypted FLAG \n|\t[W]alking with P \n|\t[J]umping over P \n|\t[Q]uit")
        ans = sc().decode().strip().lower()
        if ans == 'e':
            _P = walk(P, parameters)
            pr(border, f'The encrypted flag is: {_P.xy()}')
        elif ans == 'w':
            pr(border, 'Please send your desired point over E: ')
            Q = sc().decode().strip()
            Q = sanitize_string(Q).split(',')
            try:
                allowed_vars = {'g': g}
                Q = [eval(_, {'__builtins__': None}, allowed_vars) for _ in Q]
            except:
                die(border, 'Your input is not valid!!')
            if Q in E:
                pr(border, f'The result of the walk is: {walk(E(Q), parameters).xy()}')
            else:
                die(border, 'Your point is not on the curve E! Bye!!')
        elif ans == 'j':
            pr(border, 'Send your desired point over E: ')
            Q = sc().decode().strip()
            Q = sanitize_string(Q).split(',')
            pr(border, 'Let me know how many times you would like to jump over the given point: ')
            n = sc().decode().strip()
            try:
                allowed_vars = {'g': g}
                Q = [eval(_, {'__builtins__': None}, allowed_vars) for _ in Q]
                n = int(n)
            except:
                die(border, 'Your input is not valid!!')
            if Q in E:
                pr(border, f'The result of the jump is: {jump(E(Q), n, parameters).xy()}')
            else:
                die(border, 'Your point is not on the curve E! Bye!!')
        elif ans == 'q': die(border, "Quitting...")
        else: die(border, "Bye...")

if __name__ == '__main__':
    main()

這題就比較複雜了,是一個建立在多項式環$F_p$上的曲線,一樣要拿到生成元$P.x$
我們可以用sobata的script改一下就可以拿到$a,b,p,d$的值
前面$ax,ax^2…ax^n$的值換成一次項的係數
不過這邊要注意$a$可能不只有常數,不過機率大概一半一半
所以這邊就不寫專門對付$a$是多項式的情況了

然後可以關注一下這題$c$的區間大概在$[1,p]$,不過E的階(這邊簡稱$n$)會來到$p^2$
我們可以用pohlig-hellman來解這題
而且只要分解後的質數取夠到$c$的bit length就好了
複雜度會是$O(\sqrt{p_{max}})$,$p_{max}$是分解$n$最大質數
理論上刷到約最大的p 50bits以內的都還算短

# %%
from pwn import *
from Crypto.Util.number import *
from sage.all import *
from tqdm import trange
# io = process(['sage', 'sobataii.sage'])

io = remote('91.107.161.140', 11173)
point_fmt = "{}*g + {}, {}*g + {}"
# context.log_level = 'debug'
def str2point(s) -> tuple:
    l = [x for x in s.strip()[1:-1].decode().split(',')]
    ret = [[int(x) for x in s.split('*g + ')] for s in l]
    return tuple(ret)

def jump(P, n):
    io.sendlineafter(options, b'j')
    io.sendlineafter(b'Send your desired point over E: ', str(point_fmt.format(P[0][0], P[0][1], P[1][0], P[1][1])).encode())
    io.sendlineafter(b'Let me know how many times you would like to jump over the given point: ', str(n).encode())
    io.recvuntil(b'The result of the jump is: ')
    return str2point(io.recvline())

def walk(P):
    io.sendlineafter(options, b'w')
    io.sendlineafter(b'Please send your desired point over E: ', str(point_fmt.format(P[0][0], P[0][1], P[1][0], P[1][1])).encode())
    io.recvuntil(b'The result of the walk is: ')
    return str2point(io.recvline())


options = b"| Options: \n|\t[E]ncrypted FLAG \n|\t[W]alking with P \n|\t[J]umping over P \n|\t[Q]uit"

io.sendlineafter(options, b'e')
io.recvuntil(b'The encrypted flag is: ')
EF = str2point(io.recvline())

def point2poly(p, g):
    return (p[0][0] * g + p[0][1], p[1][0] * g + p[1][1])

def get_params():
    jumped = []
    X = EF
    for i in trange(4):
        X = jump(X, 0)
        jumped.append(X)

    for i in range(1, 3):
        S = []
        aa = jumped[i][0][0]**2 - jumped[i - 1][0][0] * jumped[i + 1][0][0]
        bb = jumped[i][1][0]**2 - jumped[i - 1][1][0] * jumped[i + 1][1][0]
        S.append(abs(aa))
        S.append(abs(bb))
    
    p = GCD(S)
    a = pow(EF[0][0], -1, p) * jumped[0][0][0] % p
    b = pow(EF[1][0], -1, p) * jumped[0][1][0] % p
    R = PolynomialRing(GF(p), names=('x',))
    x = R.gen()
    f = x**2 + 13 * x + 37
    F = GF(p**2, name='g', modulus=f)
    g = F.gen()
    
    
    EF_poly = point2poly(EF ,g)
    d = (EF_poly[1] ** 2 - EF_poly[0] ** 3) % f
    
    assert EF[0][0] * a % p == jumped[0][0][0]
    assert EF[1][0] * b % p == jumped[0][1][0]
    return p, a, b, d, F

p, a, b, d, F = get_params()

print(a, b, d, p, F)
E = EllipticCurve(F, [0, d])
n = F.order()
print(n)
phi = euler_phi(n)
print(phi)

# %%

g = F.gen()
a = int(a)
b = int(b)
d = int(d)
p = int(p)


def point_devide_ab(P):
    return ([pow(a, -1, p) * P[0][0] % p, pow(a, -1, p) * P[0][1] % p], [pow(b, -1, p) * P[1][0] % p, pow(b, -1, p) * P[1][1] % p])

EF_inv = point_devide_ab(EF)
P = jump(EF_inv, 1)

ECC_EF = E(point2poly(EF, g))
P = E(point2poly(P, g))
print(ECC_EF)
print(P)


# nE = E.order()
# print(nE)



# %%

'''
from https://github.com/elikaski/ECC_Attacks?tab=readme-ov-file#The-order-of-the-generator-is-almost-a-smooth-number-and-the-private-key-is-small
'''
print("calc order")
G = ECC_EF
n = G.order()
print(n)

print("Number of bits in n:", n.nbits())
factors = n.factor()
print("n's factors:", factors)

PRIVATE_KEY_BIT_SIZE = 196

print("We know that the private key is", PRIVATE_KEY_BIT_SIZE, "bits long")
print("Lets find which of the factors of G's order are relevant for finding the private key")
# find factors needed such that the order is greater than the secret key size
count_factors_needed = 0
new_order = 1
for p, e in factors:
    new_order *= p**e
    count_factors_needed += 1
    if new_order.nbits() >= PRIVATE_KEY_BIT_SIZE:
        print("Found enough factors! The rest are not needed")
        break
factors = factors[:count_factors_needed]
print("Considering these factors:", factors)
maxPdigit = len(str(factors[-1][0]))
print(f"max P:{maxPdigit}")
    


print("Calculating discrete log for each quotient group...")
subsolutions = []
subgroup = []
for p, e in factors:
    quotient_n = (n // p ** e)
    G0 = quotient_n * G # G0's order is p^e
    P0 = quotient_n * P
    k = G0.discrete_log(P0)
    print(k)
    subsolutions.append(k)
    subgroup.append(p ** e) # k the order of G0

print("Running CRT...")
c = crt(subsolutions, subgroup)
assert c * G == P
print(f"{c=}")



# %%
E = EllipticCurve(F, [0, d])
n = E.order()
phi = euler_phi(n)

# for test
inv_c = pow(c, phi-1, n)
G = E.random_element()
print(G)
Q = G*c
print(Q)
Q = Q*int(inv_c)
print(Q)

因為很難刷到c,所以不太確定接下來的script能不能順利跑好
所以我把接下來的參數放到另外一個script上面跑

from sage.all import *
from Crypto.Util.number import *
a,b,d,p=(27415078278705943173248686032762859913892271126336088495674,85343696207682507463081995581530292664549906625552217251278,84412988527328974803317456556577950631753400831095577228135,85343696207682507463081995581530292664549906625552217251279)
R = PolynomialRing(GF(p), names=('x',))
x = R.gen()
f = x**2 + 13 * x + 37
F = GF(p**2, name='g', modulus=f)
g = F.gen()
c = 81446674977834806094264227703761537144469572604059356266599

E = EllipticCurve(F, [0, d])
n = E.order()
phi = euler_phi(n)

EF = E(80105022105305656435804001786978544483063554811387226742138*g + 45822344950548198179313350724637284119435282504928250868227 , 27573977151832639255987910477162487928036614614340701345896*g + 48512223005936495900657109299888518641240949701285562336657)
P = E(59959918177806095898859935476069121486342386709716822159338*g + 48979991981825255322488301826955724311410886171452523085602 , 68442194321836907018203511649392289039628198892210668492569*g + 1399470228127443028376163429743970271950998458273667227608)

inv_c = pow(c, -1, n)
F = EF*int(inv_c)
ax = F.xy()[0].polynomial().coefficients()[0]
print(long_to_bytes(int(ax) * pow(int(a), -1, p) % p))
# CCTF{Ecc_5tRong_cRyPto!}