---
# System prepended metadata

title: 'RING, FIELD, ELIPTIC CURVE…'

---

# RING, FIELD, ELIPTIC CURVE... 
# Ring (Zmod) 
## Định nghĩa 
- Vành các số nguyên modulo $n$: 
## Tính chất
- $n$ nguyên tố gồm $n - 1$ phần tử 
- $n (!=) 0$ , mọi phần tử khác 0 + không có ước với $n$ thì khả nghịch 
## Zmod() in Sage 
Cú pháp: 
```sage!
R = Zmod(n) 
```
- R(a) chuyển số nguyên Python a thành phần tử trong vành
- R.cardinality() trả về $n$ 
- R.is_prime_field() kiểm tra xem $n$ có nguyên tố không
- R.extension(f) xây dựng mở rộng đại số của vành modulo đa thức f 
- 
# $Gf(p)$ Hay trường 
## Định nghĩa 
- Chỉ tồn tại <=> p là 1 số nguyên tố hoặc là 1 số nguyên tố được lũy thừa lên bậc dương. 
- Nếu chỉ có p không được lũy thừa, trở thành 1 Ring. 
- Nếu p được lũy thừa, $Gf(p^n)$ được xây dựng dưới dạng: $F_p[X]/P(X)$ 
## Tính chất 
- Mỗi trường $Gf(p^n)$ có đúng $p^n$ phần tử và mọi phần tử khác 0 đều có nghịch đảo 
- Phép Frobenius $x -> x^p$ là tự động cơ bản trong $Gf(p^n)$ và có thể truy cập qua phương thức conjugate() khi n = 2. 

## Tạo trường mở rộng $Gf(p^n)$ 
### Cú pháp cơ bản 
```sage!
K = GF(p**n, 'a')  
# hoặc rõ hơn với đa thức mô-đun
x = polygen(GF(p), 'x')
K = GF(p**n, names='a', modulus=x^n + …)
```
- ký hiệu $a$ là phần tử sinh. 
### Dùng phương thức extension 
Khi đã có $p^m$ rồi, ta có thể mở rộng thêm: 
```python!
F = GF(p)
R.<x> = F['x']                    # khai báo đa thức
E = F.extension(x^n + …, 'b')     # tạo GF(p^n) với sinh b
L = E.extension(y^m + …, 'c')     # tầng mở rộng tiếp
```
## Một số phương thức quan trọng 
- Hàm khởi tạo và ép kiểu: 
```python!
F(a)       # ép số nguyên a thành phần tử trong F

```
- Frobenius và nghịch đảo: 
```python!
b = a.conjugate()   # cho GF(q^2)
a.inverse()         # nghịch đảo
```
- Toán tử liên quan đến đa thức: 
```python!
R.polynomial_ring()    # lấy vành đa thức
I = R.ideal(f)         # lý tưởng
Q = R.quotient(I)      # trường thương
```
- Ví dụ: 
```python!
F = GF(2)
R.<x> = F['x']                         # Tạo vành đa thức GF(2)[x]
E.<a> = GF(2^4, modulus=x^4 + x + 1)   # Tạo GF(2^4) với a là phần tử sinh

print(E)           # In ra trường GF(2^4)
print(a^5)         # Luỹ thừa phần tử sinh
print(a.frobenius())  # Frobenius: a ↦ a^2 vì trường cơ sở là GF(2)
```
# Zmod, Gf Example Challenges 
## Farm - CryptoCTF 2021
Đề: 
```python!
from sage.all import *
import string, base64, math
from flag import flag

ALPHABET = string.printable[:62] + '\\='

F = list(GF(64))

def keygen(l):
    key = [F[randint(1, 63)] for _ in range(l)] 
    key = math.prod(key) # Optimization the key length :D
    return key

def maptofarm(c):
    assert c in ALPHABET
    return F[ALPHABET.index(c)]

def encrypt(msg, key):
    m64 = base64.b64encode(msg)
    enc, pkey = '', key**5 + key**3 + key**2 + 1
    for m in m64:
        enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))]
    return enc

# KEEP IT SECRET 
key = keygen(14) # I think 64**14 > 2**64 is not brute-forcible :P

enc = encrypt(flag, key)
print(f'enc = {enc}')
```
Output:
```python!
enc = "805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj"
```
### Phân tích 
 Key trong đề bài là tích random của 14 phần tử ngẫu nhiên trong GF(64). Sau đó được mod lại trở thành: $pkey = key^5 + key^3 + key^2 + 1$

Plaintext được mã hóa bằng cách: 
- Chuyển plaintext thành base64
- Với mỗi phần tử của plaintext, tìm index chứa phần tử đó trong ALPHABET, sau đó thay thế phần tử đó = F[index]. 
- Sau khi thay thế, phần tử lúc này được nhân thêm với $pkey$. Lúc này vì phép nhân được thực hiện trong $Gf(64)$ nên bản chất nó vẫn nằm trong Trường này. Sau đó, trả về index của tích này trong F, rồi lại lấy phần tử của index đó trong ALPHABET làm 1 phần tử của Ciphertext hay $enc$. 
### Solution 
Ta thấy được rằng, key trong bài chỉ có thể nằm tring GF(64), nên là không gian key chỉ có 64 phần tử. Hiểu được điều này, ta sẽ tiến hành Brute Force key cho đến khi nào giải mã được Ciphertext được thôi 

Và đồng thời, ta cũng sẽ cần phải đảo ngược lại các hàm function trong đề bài: 
- Hàm farmtomap() sẽ được mod thành: 
```python!
def farmtomap(f):
    assert f in F
    return ALPHABET[F.index(f)] 
```

Full script Sage: 
```sage!
import string 
import base64

F = list(GF(64))
ALPHABET = string.printable[:62] + '\\='
enc = "805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj"

def farmtomap(f):
    assert f in F
    return ALPHABET[F.index(f)] 
def decrypt(ciphertext, key):
    pkey = key**5 + key**3 + key**2 + 1
    m64 = ''
    for c in ciphertext:
        m64 += farmtomap(F[ALPHABET.index(c)] / pkey)
    return base64.b64decode(m64)

for f in F: 
    try:
        dec = decrypt(enc, f) 
        if b'CCTF' in dec: 
            print(dec)
    except: 
        continue
```
Flag: CCTF{EnCrYp7I0n_4nD_5u8STitUtIn9_iN_Fi3Ld!}

## Onlude - CryptoCTF 
Đề: 
```python!
from sage.all import *
from flag import flag

global p, alphabet
p = 71
alphabet = '=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$!?_{}<>'

flag = flag.lstrip('CCTF{').rstrip('}')
assert len(flag) == 24

def cross(m):
    return alphabet.index(m)

def prepare(msg):
    A = zero_matrix(GF(p), 11, 11)
    for k in range(len(msg)):
        i, j = 5*k // 11, 5*k % 11
        A[i, j] = cross(msg[k])
    return A

def keygen():
    R = random_matrix(GF(p), 11, 11)
    while True:
        S = random_matrix(GF(p), 11, 11)
        if S.rank() == 11:
            _, L, U = S.LU()
            return R, L, U

def encrypt(A, key):
    R, L, U = key
    S = L * U
    X = A + R
    Y = S * X
    E = L.inverse() * Y
    return E

A = prepare(flag)
key = keygen()
R, L, U = key
S = L * U
E = encrypt(A, key)
print(f'E = \n{E}')
print(f'L * U * L = \n{L * U * L}')
print(f'L^(-1) * S^2 * L = \n{L.inverse() * S**2 * L}')
print(f'R^(-1) * S^8 = \n{R.inverse() * S**8}')
```
Output:
```python!
E = 
[25 55 61 28 11 46 19 50 37  5 21]
[20 57 39  9 25 37 63 31 70 15 47]
[56 31  1  1 50 67 38 14 42 46 14]
[42 54 38 22 19 55  7 18 45 53 39]
[55 26 42 15 48  6 24  4 17 60 64]
[ 1 38 50 10 19 57 26 48  6  4 14]
[13  4 38 54 23 34 54 42 15 56 29]
[26 66  8 48  6 70 44  8 67 68 65]
[56 67 49 61 18 34 53 21  7 48 32]
[15 70 10 34  1 57 70 27 12 33 46]
[25 29 20 21 30 55 63 49 11 36  7]
L * U * L = 
[50  8 21 16 13 33  2 12 35 20 14]
[36 55 36 34 27 28 23 21 62 17  8]
[56 26 49 39 43 30 35 46  0 58 43]
[11 25 25 35 29  0 22 38 53 51 58]
[34 14 69 68  5 32 27  4 27 62 15]
[46 49 36 42 26 12 28 60 54 66 23]
[69 55 30 65 56 13 14 36 26 46 48]
[25 48 16 20 34 57 64 62 61 25 62]
[68 39 11 40 25 11  7 40 24 43 65]
[54 20 40 59 52 60 37 14 32 44  4]
[45 20  7 26 45 45 50 17 41 59 50]
L^(-1) * S^2 * L = 
[34 12 70 21 36  2  2 43  7 14  2]
[ 1 54 59 12 64 35  9  7 49 11 49]
[69 14 10 19 16 27 11  9 26 10 45]
[70 17 41 13 35 58 19 29 70  5 30]
[68 69 67 37 63 69 15 64 66 28 26]
[18 29 64 38 63 67 15 27 64  6 26]
[ 0 12 40 41 48 30 46 52 39 48 58]
[22  3 28 35 55 30 15 17 22 49 55]
[50 55 55 61 45 23 24 32 10 59 69]
[27 21 68 56 67 49 64 53 42 46 14]
[42 66 16 29 42 42 23 49 43  3 23]
R^(-1) * S^8 = 
[51  9 22 61 63 14  2  4 18 18 23]
[33 53 31 31 62 21 66  7 66 68  7]
[59 19 32 21 13 34 16 43 49 25  7]
[44 37  4 29 70 50 46 39 55  4 65]
[29 63 29 43 47 28 40 33  0 62  8]
[45 62 36 68 10 66 26 48 10  6 61]
[43 30 25 18 23 38 61  0 52 46 35]
[ 3 40  6 45 20 55 35 67 25 14 63]
[15 30 61 66 25 33 14 20 60 50 50]
[29 15 53 22 55 64 69 56 44 40  8]
[28 40 69 60 28 41  9 14 29  4 29]
```
### Phân tích 
- Flag có độ dài 24 

Đề bài chuyển Flag thành ma trận A, và sẽ tiến hành encrypt nó như sau: 
- $E = U(A + R)$ 
- $HINT1 = L.U.L$ 
- $HINT2 = L^{-1}.S^2.L$ 
- $HINT3 = R^{-1}.S^8$ 

Với đống hints này, ta dễ dàng khôi phục được $U, R$ để lấy được $A$ từ đó khôi phục lại Flag. 

Bằng 1 số bước biến đổi, ta được: 
- $U = HINT2/HINT1$
- ![image](https://hackmd.io/_uploads/rkqT3nYbge.png)
### Solution 
```python!
from sage.all import * 

E = Matrix(GF(71),[[25,55,61,28,11,46,19,50,37,5,21],
[20,57,39,9,25,37,63,31,70,15,47],
[56,31,1,1,50,67,38,14,42,46,14],
[42,54,38,22,19,55,7,18,45,53,39],
[55,26,42,15,48,6,24,4,17,60,64],
[1,38,50,10,19,57,26,48,6,4,14],
[13,4,38,54,23,34,54,42,15,56,29],
[26,66,8,48,6,70,44,8,67,68,65],
[56,67,49,61,18,34,53,21,7,48,32],
[15,70,10,34,1,57,70,27,12,33,46],
[25,29,20,21,30,55,63,49,11,36,7]])

hint1 = Matrix(GF(71),[[50,8,21,16,13,33,2,12,35,20,14],
[36,55,36,34,27,28,23,21,62,17,8],
[56,26,49,39,43,30,35,46,0,58,43],
[11,25,25,35,29,0,22,38,53,51,58],
[34,14,69,68,5,32,27,4,27,62,15],
[46,49,36,42,26,12,28,60,54,66,23],
[69,55,30,65,56,13,14,36,26,46,48],
[25,48,16,20,34,57,64,62,61,25,62],
[68,39,11,40,25,11,7,40,24,43,65],
[54,20,40,59,52,60,37,14,32,44,4],
[45,20,7,26,45,45,50,17,41,59,50]])

hint2=Matrix(GF(71),[[34,12,70,21,36,2,2,43,7,14,2],
[1,54,59,12,64,35,9,7,49,11,49],
[69,14,10,19,16,27,11,9,26,10,45],
[70,17,41,13,35,58,19,29,70,5,30],
[68,69,67,37,63,69,15,64,66,28,26],
[18,29,64,38,63,67,15,27,64,6,26],
[0,12,40,41,48,30,46,52,39,48,58],
[22,3,28,35,55,30,15,17,22,49,55],
[50,55,55,61,45,23,24,32,10,59,69],
[27,21,68,56,67,49,64,53,42,46,14],
[42,66,16,29,42,42,23,49,43,3,23]])

hint3=Matrix(GF(71),[[51,9,22,61,63,14,2,4,18,18,23],
[33,53,31,31,62,21,66,7,66,68,7],
[59,19,32,21,13,34,16,43,49,25,7],
[44,37,4,29,70,50,46,39,55,4,65],
[29,63,29,43,47,28,40,33,0,62,8],
[45,62,36,68,10,66,26,48,10,6,61],
[43,30,25,18,23,38,61,0,52,46,35],
[3,40,6,45,20,55,35,67,25,14,63],
[15,30,61,66,25,33,14,20,60,50,50],
[29,15,53,22,55,64,69,56,44,40,8],
[28,40,69,60,28,41,9,14,29,4,29]])
alphabet = '=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$!?_{}<>'
U = hint2 / hint1 
R_inverse = hint3/U/hint1/U/hint1/U/hint1/U/hint1
R = R_inverse.inverse() 
def recover_flag(A):
    flag_chars = ''
    for k in range(24):
        i, j = 5*k // 11, 5*k % 11
        idx = int(A[i, j])  # phần tử thuộc GF(p), cần cast về int
        flag_chars += (alphabet[idx])
    return 'CCTF{' + (flag_chars) + '}'

A = (U.inverse())*E - R 
recover_flag(A)
```


# Permutation Cipher 
## Định nghĩa
Tái sắp xếp các ký tự của plaintext nhưng không thay đổi ký tự. Khi key chính là 1 permutation có kích thước là $e$, ta chia plaintext thành các khối có độ dài là $e$ rồi hoán vị theo theo key đó. 
## Cách hoạt động 
- Chia khối: Cho plaintext thành các khối có cùng độ dài bằng độ dài key, có thể thêm các ký tự null nếu không đủ. 
- Hoán vị: Với mỗi khối, sắp xếp lại ký tự theo permutation key: ví dụ key “4312” nghĩa là ký tự thứ nhất của khối mới lấy từ vị trí 4 của khối cũ, v.v. 
- Đọc kết quả 
> Trong nhiều trường hợp, khi cách mã hóa này áp dụng trên 1 string cố định, thì nếu biết được cách mà nó mã hóa ta có thể áp dụng nhiều lần hàm mã hóa này lên Ciphertext thì đến 1 thời điểm nào đó nó sẽ quay trở lại thành Plaintext ban đầu. Đây là tính chất của Permutation Cipher. 
# Permutation Cipher Example Challenges
## Transposed - Jordan & Tunisia National CTF
Đề: 
```python!
import random

W = 7
perm = range(W)
random.shuffle(perm)

msg = open("flag.txt").read().strip()
while len(msg) % (2*W):
    msg += "."

for i in xrange(100):
    msg = msg[1:] + msg[:1]
    msg = msg[0::2] + msg[1::2]
    msg = msg[1:] + msg[:1]
    res = ""
    for j in xrange(0, len(msg), W):
        for k in xrange(W):
            res += msg[j:j+W][perm[k]]
    msg = res
print msg
```
### Phân tích 
```python!
W = 7
perm = range(W)
random.shuffle(perm)
```
- Tạo bảng hoán vị cho toàn bộ mã hóa 
```python!
msg = open("flag.txt").read().strip()
while len(msg) % (2*W):
    msg += "."
```
- Flag hay $msg$ sẽ được padd cho đủ 14 phần tử. 
```python!
for i in xrange(100):
    msg = msg[1:] + msg[:1]
    msg = msg[0::2] + msg[1::2]
    msg = msg[1:] + msg[:1]
    res = ""
    for j in xrange(0, len(msg), W):
        for k in xrange(W):
            res += msg[j:j+W][perm[k]]
    msg = res
```
Trong mỗi vòng lặp: 
- $msg$ bị xoay trái 1 đơn vị 
- Sau đó, tách các phần tử chẵn và lẻ lần lượt rồi ghép chúng vào với nhau
- Xoay trái thêm 1 phát nữa 
- Trong vòng lặp con: 
- - Chia $msg$ thành các khối có 7 phần tử, sau đó tiến hành hoán vị các ký tự theo bảng hoán vị. 
### Solution
Vì mọi hoán vị diễn ra trên 1 string cố định, nên với $d$ lần hoán vị tương đương string sẽ trở về trạng thái ban đầu. Ta đơn giản chỉ cân bruteforce Permutations_map rồi dùng nó hoán vị nhiều lần Ciphertext là được: 
```python!
res, sol = [], []
def gen_permu(n): 
    if len(sol) == n: 
        res.append(sol.copy()) 
        return
    
    for i in range(n): 
        if i not in sol: 
            sol.append(i) 
            gen_permu(n) 
            sol.pop() 
gen_permu(7)  
permutations = res
W = 7 
msg_enc = "L{NTP#AGLCSF.#OAR4A#STOL11__}PYCCTO1N#RS.S"
for perm in permutations:
    msg = msg_enc
    for i in range(100):
        msg = msg[1:] + msg[:1]
        msg = msg[0::2] + msg[1::2]
        msg = msg[1:] + msg[:1]
        res = ""
        for j in range(0, len(msg), W):
            for k in range(W):
                res += msg[j:j+W][perm[k]]
        msg = res
        if "FLAG{" in msg: 
            print(res) 
```
Flag = FLAG{##CL4SS1CAL_CRYPTO_TRANSPOS1T1ON##}
## Permutation[452] - Grey Cat The Flag 2022
Đề: 
```python!
from secrets import randbits
from hashlib import shake_256
import random
import perm

FLAG = <REDACTED>

def encrypt(key : str) -> str:
    otp = shake_256(key.encode()).digest(len(FLAG))
    return xor(otp, FLAG).hex()

def xor(a : bytes, b : bytes) -> bytes:
    return bytes([ x ^ y for x, y in zip(a, b)])

n = 5000
h = 2048

arr = [i for i in range(n)]
random.shuffle(arr)

g = perm.Perm(arr)
a = randbits(h); b = randbits(h)
A = g ** a; B = g ** b
S = A ** b
key = str(S)

print(f"g = [{g}]"); print(f"A = [{A}]"); print(f"B = [{B}]"); 

print(f"c = {encrypt(key)}")
```
File perm: 
```python!
class Perm():
    def __init__(self, arr):
        assert self.valid(arr)
        self.internal = arr
        self.n = len(arr)

    def valid(self, arr):
        x = sorted(arr)
        n = len(arr)
        for i in range(n):
            if (x[i] != i):
                return False
        return True

    def __str__(self):
        return ",".join(map(str, self.internal))

    def __mul__(self, other):
        assert other.n == self.n
        res = []
        for i in other.internal:
            res.append(self.internal[i])
        return Perm(res)

    def __pow__(self, a):
        res = Perm([i for i in range(self.n)])
        g = Perm(self.internal)
        while (a > 0):
            if (a & 1): res = res * g
            g = g * g
            a //= 2
        return res
```
Output: 
```python!
g = ... 
A = ... 
B = ...
c = 9f8a883d7e045010619a7aba5c0cdeb33ee0482626e2c5e718b3ef955ad9b4986d4406b6a1f53e78e506c7dcf806f964090a1e44fe2737b883
```
### Phân tích 




# Afffine Cipher 
## Định nghĩa 
Sử dụng hàm tuyến tính trên tập giá trị số của ký tự: 
$E(x) = a.x + b \mod m$ 
- m là kịch thước của bảng chữ cái. 
- $a, b$ là khóa. 
- $GCD(a, m) = 1$ để có thể giải mã
## Cách hoạt động 
1. Chuẩn hóa: Gán giá trị số cho ký tự: ví dụ A=0, B=1,…, Z=25. 
2. Mã hóa: Vỡi mối giá trị của x, có $y = (ã + b) \mod m$. 
3. Ghép lại 
## A Fine Cipher - RITSEC CTF 2023
ĐỀ: ![image](https://hackmd.io/_uploads/Skpd7s8Mxl.png)
### Phaân tích: 
Ta lấy 1 câu đơn giản làm mẫu trước. Khi gặp những đề bài thế này + gợi ý cách encrypt thì ta sẽ dùng `dcode` để mò thôi 
### Solution 
![image](https://hackmd.io/_uploads/BJi2rsUzxl.png)
FLAG: RS{IFYOUAREINTERESTEDCHECKOUTMORECRYTPOCTFSATCRYPTOHACK} 

## zakukozh_DONE 
ĐỀ: 
Đề bài cho ta 1 file nhị phân bị mã hóa Cipher: 
`zakukozh.bin` 
Nhiệm vụ của ta là giải mã nó.
### Phân tích 
Ta biết rằng mỗi phần tử của Affine Cipher được mã hóa theo công thức: 
$E(x) = a.x + b \mod m$ 

- Đây là file nhị phân, ta đoán rằng $m = 256$ 
- $a, b$ sẽ thử bruteforce trong khoảng $m$ 
- Đồng thời, nếu đây là file ảnh `jpg`, 2 bytes đầu sẽ là `255, 216`, nếu là png sẽ là `137, 80`. 
- Với mỗi $a, b$ lụm được, ta thử giải mã toàn bộ file để lấy ảnh và chọn ảnh có flag. 
### Solution 
- Bruteforce $a, b$: 
```python
from math import gcd 

data  = bytearray(open('zakukozh.bin', 'rb').read()) 
m = 256 

def affine(x, a , b): 
    return (a*x + b) % m 

for a in range(m): 
    if gcd(a, m) != 1: 
        continue 
    for b in range(m): 
        new = [affine(data[0], a ,b), affine(data[1],a, b)] 
        if new == [255, 216]: 
            print("jpg", a, b) 
        elif new == [137, 80]: 
            print("png", a, b)  
```

- Output: 
```python
jpg 177 159
png 239 233
``` 

- Final check: 
```python
jpg = bytearray([affine(x, 177, 159) for x in list(data)]) 
png = bytearray([affine(x, 239, 233) for x in list(data)]) 
open("possible_jpg.jpg", "wb").write(jpg)
open("possible_png.png", "wb").write(png)
``` 
Flag: ![possible_png](https://hackmd.io/_uploads/B1CNxn8fge.png)

# Eliptic Curve Cryptography -CryptoHack 
# Background Reading 
> ECC không tự nhiên mà đến, mà mày phải là người tìm nó 

Khi nghĩ đến ECC, ta tưởng tượng 1 đường cong Ellip. Ta sẽ nghiên cứu phương trình `Weierstrass`: $E:y^2 = x^3 + a.x + b$ 
- Đặc biệt: $4.a^3 + 27.b^2 != 0$

Elliptic Curve là tập hợp các điểm thỏa mãn phương trình trên trong 1 trường hữu hạn 

`Point addition`: Cho 2 điểm P và Q đều nằm trên Elliptic Curve, ta định nghĩa 1 điểm thứ 3 là R = P + Q cũng nằm trên đường cong đó(Phép công này tuân theo cấu trúc Abel):
![image](https://hackmd.io/_uploads/BymavywMel.png)

Khi nhân 1 điểm với 1 số nguyên(Hay scaling), ta có thể lấy 1 VD:
$Q = [2].P = P + P$ 
Điểm mấu chốt ở đây là: Khi cho $Q, P$ và bắt tìm scalar $n$ thì rất khó vì đây chính là bẫy 1 chiều Trapdoor của hệ thống Crypto này. Toàn bộ hệ thống sẽ dựa vào độ khó của việc tìm $n$, hay n thực chất sẽ chính là ẩn số hay $private$ mà kẻ tấn công cần tìm. 


`Understanding Point Addition`: 
- Chọn 2 điểm $P, Q$ trên Elliptic Curve.
- Nối 2 đểm này thành 1 đường thẳng và điểm thứ 3 $R$ chính là giao điểm của đường $PQ$ với Elliptic Curve. 
- Lấy $R'$ là nghịch đảo $y$ của $R$, ta được $R' = (x_R, -y_R)$. 
- $P + Q = R'$ -> Đây chính là cách ta hiểu về `point addition` 

`VẬY CÒN NẾU TA CỘNG 1 ĐIỂM VỚI CHÍNH NÓ THÌ SAO?`
- Gỉa sử ta cộng $P + P$, vậy ta không thể vẽ đường thẳng qua 2 điểm khác nhau được. 
- Nên thay vào đó, lấy tiếp tuyến với Elliptic Curve tại chính điểm $P$ đó. 
- Và cứ thế theo logic cũ, đường tiếp tuyến này rồi sẽ giao với Elliptic Curve tại 1 điểm là $R$. Ta được: $P + P = R'$

`VẬY NẾU PQ HAY PP KHÔNG CẮT ELLIPTIC CURVE TẠI ĐIỂM NÀO THÌ SAO?` 
(VD: P & Q đối xứng qua trục hoành)
- Lúc này ta lấy điểm thứ 3 chính là điểm vô cực $O$ - hay điểm đơn vị. 
- Phần tử $O$ được định nghĩa: $P + (-P) = O$

![image](https://hackmd.io/_uploads/rJQDVxvMle.png)

Flag: Cờ của đề bài là tên nhóm có tính chất giao hoán, vậy thì sẽ là: `crypto{Aelian}` 
# Starter
## Point Negation 
Đoạn này cơ bản giống phần Background, nhấn mạnh 1 chút rằng ta đang nghiên cứu ECC nên các định nghĩa đều phải để trong $Fp$. 

Bài tập ở phần này là tìm $Q$ sao cho với P cho trước thỏa mãn $P + Q = O$ -> Tức là $Q = -P$  

Ta đơn giản chỉ cần: Lấy nghịch đảo của $P$ đơn giản là là $P' =  P(x, -y)$ 

Vậy với $P(8045,6936)$ ta được Flag là: `crypto{8045,2803}
`

## Point Addition 
Để tính tổng 2 điểm trong Elliptic Curve. Ta có thể sử dụng 1 trong 2 cách: 

- Đầu tiên là sử dụng Hàm tự xây dựng: 
```python
def modinv(a, p):
    # Nghịch đảo modulo dùng extended Euclidean algorithm
    return pow(a, -1, p)

def ec_add(P, Q, a, p):
    # Nếu một trong hai điểm là điểm "vô cực", trả lại điểm kia
    if P is None:
        return Q
    if Q is None:
        return P
    
    x1, y1 = P
    x2, y2 = Q

    # Trường hợp P = -Q → kết quả là điểm vô cực
    if x1 == x2 and (y1 + y2) % p == 0:
        return None

    if P != Q:
        # Slope λ = (y2 - y1) / (x2 - x1)
        numerator = (y2 - y1) % p
        denominator = (x2 - x1) % p
        lam = (numerator * modinv(denominator, p)) % p
    else:
        # P == Q → dùng công thức tiếp tuyến
        numerator = (3 * x1 * x1 + a) % p
        denominator = (2 * y1) % p
        lam = (numerator * modinv(denominator, p)) % p

    # Tính x3, y3
    x3 = (lam * lam - x1 - x2) % p
    y3 = (lam * (x1 - x3) - y1) % p

    return (x3, y3)
```
- Sử dụng Sagemath: VD câu này lun:
```sage
from sage.all import * 

p = 9739 
F = FiniteField(p) 

E = EllipticCurve(F, [497, 1768]) 
P = E(493,5564) 
Q = E(1539,4742) 
R = E(4403,5202) 
S = P + P + Q + R
print(S)
``` 
Flag: crypto{4215,2162} 
## Scalar Multiplication 
Hàm giả được cung cấp để tính Scaling, but we gonna use Sage instead: 

![image](https://hackmd.io/_uploads/B1W4qVDGex.png)

Solution: 
```sage
from sage.all import * 

a = 497 
b = 1768 
p = 9739 

E = EllipticCurve(GF(p), [a, b]) 
P = E(2339,2213) 
print(7863*P)
```
Flag: crypto{9467,2742} 
## Curves and Logs 
Bài toán logarit rời nhạc trong Elliptic Curve: `The Elliptic Curve Discrete Logarithm Problem (ECDLP)` là bài toán tìm $n$ trong $Q = n.P trong ECC. 

`Thuật toán DFH`: 
Cả 2 bên sẽ đồng ý các tham số sau: 
- Elliptic Curve: $E$ 
- Prime: $p$ 
- 1 Hàm sinh $G \in E$ với bậc (order) là số nguyên tố $q$ => $H = <G>$ là nhóm con được sinh ra bởi $G$, gồm tất cả $nG$ với $n \in Zq$

Lúc này, cả 2 bên sẽ chọn cho mình các $n$, lúc này logic encrypt và decrypt sẽ khá giống `DFH`: 
    
- Alice chọn $n_A$, tính $Q_A = n_A.G$ rồi gửi cho Bob. 
- Bob cũng tương tự, Alice sẽ nhận được $Q_B = n_B.G$ 
- Lúc này thì mỗi người nhân Gía trị mà mình nhận được với khóa riêng là đã có thể đều nhìn thấy được cùng 1 Giá trị. 

Đề bài phần này yêu cầu ta tính toán tạo độ $x$ của $S$ với điều kiện biết trước EC và $G \& n$. 
    
```python
from sage.all import * 
from hashlib import sha1
a = 497 
b = 1768 
p = 9739 

E = EllipticCurve(GF(p), [a, b]) 
Qa = E(815,3190) 
nb = 1829 
flag = int((Qa*nb)[0]) 
flag = sha1((str(flag).encode())).hexdigest() 
print(flag)
``` 
Flag: crypto{80e5212754a824d3a4aed185ace4f9cac0f908bf}  

## Efficient Exchange 

Với tất cả giá trị $x$ mà Alice và Bob nhận được, chỉ có thể có 2 giá trị của $y$ ứng với nó. 

Đề bài cho ta giá trị: $x_A$ của $Q_A$ và $n_B$. Nhiệm vụ của ta là phải tìm lại Plaintext ban đầu với việc được cho trước hàm decrypt và các dữ liệu liên quan. 

```sage
from sage.all import * 
from hashlib import sha1
a = 497 
b = 1768 
p = 9739 
F = FiniteField(p)
E = EllipticCurve(GF(p), [a, b]) 
x = F(4726) 
n_b = F(6534) 
ysqrt = x**3 + a*x + b 
y = (ysqrt.sqrt(all = True))[0] 
S = E(x,y)*n_b 
print(S)
``` 
Đây là hàm tính giá trị của y, từ đó có được Shared để nhét vào file `decrypt`: 

Final:
```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib


def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))


def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Decrypt flag
    ciphertext = bytes.fromhex(ciphertext)
    iv = bytes.fromhex(iv)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)

    if is_pkcs7_padded(plaintext):
        return unpad(plaintext, 16).decode('ascii')
    else:
        return plaintext.decode('ascii')


shared_secret = 1791
iv = 'cd9da9f1c60925922377ea952afc212c'
ciphertext = 'febcbe3a3414a730b125931dccf912d2239f3e969c4334d95ed0ec86f6449ad8'

print(decrypt_flag(shared_secret, iv, ciphertext)
``` 
Flag: crypto{3ff1c1ent_k3y_3xch4ng3} 
    
# Parameter Choice 
## Smooth Criminal 

Đề bài này cho ta thông tin về Public của Bob, Public của Alice, G, Elliptic Curve và các thông tin râu ria để Decrypt Ciphertext 

Mục tiêu chính của ta lần này đó chính là làm thế nào để tìm lại được private $n$ của Alice, biết G và Public. 
    
`Bài toán logarit rời rạc trong ECC - ECDLP` 

Điều kiện triển khai: 
- Các điểm phải nằm trên Elliptic Curve Fp cho trước 
- G sinh ra 1 nhóm con và Public phải thuộc nhóm đó 
- Xác định được order - bậc của G:
    - Nhỏ: bruteforce 
    - Smooth: Pohlig-Hellman  
    - Prime to vừa: BigstepGiantstep 

-> Nma thật ra ta có thể thử giải bằng Sagemath và tùy vào thời gian chờ đợi kết quả hay việc có ra được kết quả hay không sẽ cho ta biết độ khó hay độ khả thi của Bài toán này. 
    
```SAGE
from sage.all import * 
from hashlib import sha1
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib

def decrypt_flag(shared_secret: int, iv_hex: str, ciphertext_hex: str):
    # Tạo AES key từ shared_secret (giống y trong encrypt_flag)
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]

    # Giải mã AES
    iv = bytes.fromhex(iv_hex)
    ciphertext = bytes.fromhex(ciphertext_hex)

    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext_padded = cipher.decrypt(ciphertext)
    
    try:
        plaintext = unpad(plaintext_padded, 16)
    except ValueError as e:
        raise ValueError("Sai shared secret hoặc ciphertext bị lỗi padding!") from e

    return plaintext

p = 310717010502520989590157367261876774703
a = 2
b = 3
F = FiniteField(p)
E = EllipticCurve(GF(p), [a, b]) 
g_x = 179210853392303317793440285562762725654
g_y = 105268671499942631758568591033409611165
G = E(g_x, g_y) 
order  = G.order() 
public = E(280810182131414898730378982766101210916, 291506490768054478159835604632710368904) 
n = G.discrete_log(public)
b_x = 272640099140026426377756188075937988094
b_y = 51062462309521034358726608268084433317
Bob_public = E(b_x, b_y) 
iv = '07e2628b590095a5e332d397b8a59aa7'
encrypted_flag = '8220b7c47b36777a737f5ef9caa2814cf20c1c1ef496ec21a9b4833da24a008d0870d3ac3a6ad80065c138a2ed6136af'
print(decrypt_flag((Bob_public*n)[0], iv, encrypted_flag))
```
Flag: crypto{n07_4ll_curv3s_4r3_s4f3_curv3s} 
    
## Exceptional Curves 

Câu này em ăn gian 1 xíu, hẹ hẹ Sagemath no.1: 
    
```sage
from sage.all import * 
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib
from binascii import unhexlify

def decrypt_flag(b_x, b_y, iv_hex, ciphertext_hex, nA, E):
    # Reconstruct Bob's public key
    B = E(b_x, b_y)
    
    # Compute shared secret
    S = B * nA
    x_shared = S.xy()[0]

    # Derive AES key
    sha1 = hashlib.sha1()
    sha1.update(str(x_shared).encode('ascii'))
    key = sha1.digest()[:16]

    # Convert IV and ciphertext
    iv = unhexlify(iv_hex)
    ciphertext = unhexlify(ciphertext_hex)

    # AES decryption
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = unpad(cipher.decrypt(ciphertext), 16)
    return plaintext

# Curve params
p = 0xa15c4fb663a578d8b2496d3151a946119ee42695e18e13e90600192b1d0abdbb6f787f90c8d102ff88e284dd4526f5f6b6c980bf88f1d0490714b67e8a2a2b77
a = 0x5e009506fcc7eff573bc960d88638fe25e76a9b6c7caeea072a27dcd1fa46abb15b7b6210cf90caba982893ee2779669bac06e267013486b22ff3e24abae2d42
b = 0x2ce7d1ca4493b0977f088f6d30d9241f8048fdea112cc385b793bce953998caae680864a7d3aa437ea3ffd1441ca3fb352b0b710bb3f053e980e503be9a7fece

# Define curve
E = EllipticCurve(GF(p), [a, b])
PublicKey = E(4748198372895404866752111766626421927481971519483471383813044005699388317650395315193922226704604937454742608233124831870493636003725200307683939875286865 , 2421873309002279841021791369884483308051497215798017509805302041102468310636822060707350789776065212606890489706597369526562336256272258544226688832663757)
G = E(3034712809375537908102988750113382444008758539448972750581525810900634243392172703684905257490982543775233630011707375189041302436945106395617312498769005, 4986645098582616415690074082237817624424333339074969364527548107042876175480894132576399611027847402879885574130125050842710052291870268101817275410204850) 
b_x = 0x7f0489e4efe6905f039476db54f9b6eac654c780342169155344abc5ac90167adc6b8dabacec643cbe420abffe9760cbc3e8a2b508d24779461c19b20e242a38
b_y = 0xdd04134e747354e5b9618d8cb3f60e03a74a709d4956641b234daa8a65d43df34e18d00a59c070801178d198e8905ef670118c15b0906d3a00a662d3a2736bf 
na = G.discrete_log(PublicKey) 
iv = '719700b2470525781cc844db1febd994'
encrypted_flag = '335470f413c225b705db2e930b9d460d3947b3836059fb890b044e46cbb343f0' 
print(decrypt_flag(b_x, b_y, iv, encrypted_flag, na, E))
``` 
Flag: crypto{H3ns3l_lift3d_my_fl4g!} 
    
## Micro Transmissions 

Câu này hướng tới việc sử dụng thuật toán Pohlig Hellman  
    
Thuật toán Pohlig Hellman dành cho điều kiện private key $n$ yếu: 
```sage
order =  G.order()
print(order) 
print(factor(order))
max_value_n = 2**64 

results = []
factors = []
mul = 1
for prime, exponent in factor(order):
    e = (order//(prime**exponent))
    G_new = G*e
    A_new = A*e
    dlog = G_new.discrete_log(A_new)
    print(prime, dlog)
    results.append(dlog)
    factors.append(prime**exponent)
    mul *= prime
    if mul > max_value_n:
        break

n_a = int(crt(results, factors))
print(n_a)
``` 
    
Full script: 
```sage
from sage.all import * 
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib

def decrypt_flag(iv_hex: str, ciphertext_hex: str, shared_secret: int) -> bytes:
    """
    Giải mã AES-CBC của flag từ IV, ciphertext (cả hai ở dạng hex) và shared secret (int).
    Trả về plaintext (FLAG) ở dạng bytes.
    """
    # Derive AES key từ shared_secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]

    # Chuyển IV và ciphertext từ hex về bytes
    iv = bytes.fromhex(iv_hex)
    ciphertext = bytes.fromhex(ciphertext_hex)

    # Giải mã và bỏ padding
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = (cipher.decrypt(ciphertext), AES.block_size)
    return plaintext

# Curve params
p = 99061670249353652702595159229088680425828208953931838069069584252923270946291
a = 1
b = 4
F = FiniteField(p)
# Define curve
E = EllipticCurve(GF(p), [a, b])
x = F(87360200456784002948566700858113190957688355783112995047798140117594305287669)
ysqrt = x**3 + a*x + b 
y1 = (ysqrt.sqrt(all = True))[0] 
y2 = (ysqrt.sqrt(all = True))[1]
G = E(43190960452218023575787899214023014938926631792651638044680168600989609069200 , 20971936269255296908588589778128791635639992476076894152303569022736123671173) 
A = E(87360200456784002948566700858113190957688355783112995047798140117594305287669, y2)
xb = F(6082896373499126624029343293750138460137531774473450341235217699497602895121)
ybsqrt = xb**3 + a*xb + b  
B = E(xb, ybsqrt.sqrt(all = True)[0]) 

order =  G.order()
print(order) 
print(factor(order))
max_value_n = 2**64 

results = []
factors = []
mul = 1
for prime, exponent in factor(order):
    e = (order//(prime**exponent))
    G_new = G*e
    A_new = A*e
    dlog = G_new.discrete_log(A_new)
    print(prime, dlog)
    results.append(dlog)
    factors.append(prime**exponent)
    mul *= prime
    if mul > max_value_n:
        break

n_a = int(crt(results, factors))
print(n_a)
def gen_shared_secret(P, n):
	S = n*P
	return S.xy()[0]
iv = 'ceb34a8c174d77136455971f08641cc5'
encrypted_flag = 'b503bf04df71cfbd3f464aec2083e9b79c825803a4d4a43697889ad29eb75453' 
shared = gen_shared_secret(B, n_a) 
print(decrypt_flag(iv, encrypted_flag, shared))
``` 
Flag: crypto{d0nt_l3t_n_b3_t00_sm4ll} 

## Elliptic Nodes 
Phân này yêu cầu ta phải giải 2 bài toán: 
- Tìm lại $a, b$ của EC cho trước biết Public và $G$
- Với a, b tìm được, Ta giải bài toán logarit rời nhạc với EC dạng `Singular`

### Bài toán tìm $a, b$ 

![image](https://hackmd.io/_uploads/HyVXMaOfxl.png)
Khá dễ, chỉ cần giải phương trình modulo là xong. 
    
### Bài toán tính Logarit rời rạc với EC dạng Singular 

Ý tưởng: Chuyển bài toán từ `ECDLP` sang bài toán logarit rời rạc trong $F(p, *)$ ->Sagemath có thể giải bài toán này dễ hơn nhiều:
- Ta sẽ phải shift EC của ta thành conic: $y^2 = x'^2(x' + C)$. 
    - Nhóm này rất đặc biệt, nó tham số hóa toàn bộ các điểm nằm trên đó bằng 1 phân tử trong $Fp^*$. 
    - Nói cách khác: Nó chuyển tử nhóm cộng EC sang nhóm nhân $Fp$ 
- Đặt $w = \frac{y}{x'}$, ta có thể biến đổi được thành: $z = \frac{y - t.x'}{y + t.x'}$ với ($t = \sqrt{C}$) . Đây là ánh xạ `Mobius` - Mỗi 1 cặp $(y, x')$ ánh xạ thành 1 giá trị $z \in F_p^*$. 
- Ánh xạ $G, Q_A$ sang $F_p^*$, giải logarit rời rạc để lụm $d$ 

Vậy thì $x' = x + s$ thì ta nên chọn $s$ là bao nhiêu? 
    
Quay trở lại với phương trình EC ban đầu, ta có: 
$y^2 = x^3 + a.x + b$, ta thử thay $x = x' + s$ vào nó: 
Ta được: 
- $x^3 + a.x + b = (x' + s)^3 + a.(x' + s) + b$ 
    - $x^3 + a.x + b = x'^3 + 3.s.x'^2 + (3s^2 + a).x' + (s^3 + a.s + b)$ 
    - Để có thể chuyển về dạng đường `conic` thì buộc: $3s^2 + a$ và $s^3 + a.s + b$ phải $= 0$ trong $Fp$ 
    - Vậy đồng nghĩa với việc $s$ hay hệ số shift $x$ phải là nghiệm của: $s^3 + a.s + b$
- Khi tìm được $s$, ta được đường `conic` là: $x'^2(x' + 3s)$
- Nhiệm vụ cuối cùng của ta lúc này là ánh xạ $Q_A$ và $G$ để lấy loga thôi. 

### Solution 
```sage
from sage.all import * 
from Crypto.Util.number import long_to_bytes

xq = 2582928974243465355371953056699793745022552378548418288211138499777818633265
yq = 2421683573446497972507172385881793260176370025964652384676141384239699096612
p = 4368590184733545720227961182704359358435747188309319510520316493183539079703

xg = 8742397231329873984594235438374590234800923467289367269837473862487362482
yg = 225987949353410341392975247044711665782695329311463646299187580326445253608 

a = ((-(yg**2 - yq**2) - (xq**3 - xg**3) % p)*pow(xq - xg, -1, p)) % p  
b = (yg**2 - xg**3 - a*xg) % p
R = PolynomialRing(GF(p), 'x')
x = R.gen()
f = x**3 + a*x + b 
s = f.roots() 
s1 = s[0][0] 
s2 = s[1][0]
shift = 0
if GF(p)(s1*3).is_square(): 
    shift = s1
if GF(p)(s2*3).is_square(): 
    shift = s2 
newf = f.subs(x = x + shift) 
t = GF(p)(3*shift).square_root() 
zxq = (xq - shift) 
zxg = (xg - shift) 
zQ = (yq - t*zxq)/(yq + t*zxq) % p 
zG = (yg - t*zxg)/(yg + t*zxg) % p
d = discrete_log(zQ, zG)
print(long_to_bytes(d)) 
```
Flag: crypto{s1ngul4r_s1mplif1c4t1on} 

## Moving Problems
Với cái đề bài như thế này gợi ý luôn cho ta rằng Ta cần ứng dụng `MOV - Menezes–Okamoto–Vanstone` Attack để giải. 

### MOV Attack 
Dạng tấn công này xuất hiện không nhiều, chủ yếu đánh vào các curve có embedding level nhỏ: 
$$
r | p^k - 1
$$ 

- Câu này ta tính được $k = 2$, vậy thì ta hoàn toàn có thể áp dụng được thuật toán MOV để tấn công. 
    
Thuật toán: 
```sage
#               Đây là Thuật Toán MOV 
# Find embedding degree k
Gn = G.order()
k = 1
while p^k % Gn != 1:
    k += 1
print("Found k:", k)

# Select private key, and calculate public key Q
Q = E(1110072782478160369250829345256, 800079550745409318906383650948)

# Define new curve mod p^k and the points on it
Ek = EllipticCurve(GF(p ^ k), [a, b])
Gk = Ek(G)
Qk = Ek(Q)
Rk = Ek.random_point()

# Find a point T with order d such that d divides G's order
m = Rk.order()
d = gcd(m, Gn)
Tk = (m // d) * Rk
assert Tk.order() == d
assert (Gn*Tk).is_zero() # Point INFINITY

# Using T, pair G and Q to integers g and q such that q=g^n (mod p^k)
g = Gk.weil_pairing(Tk, Gn)
q = Qk.weil_pairing(Tk, Gn)
# Alternatively:
#g = Gk.tate_pairing(Tk, Gn, k)
#q = Qk.tate_pairing(Tk, Gn, k)

print("Calculating private key...")
found_key = q.log(g)
print((found_key)) 
```
> Lưu ý nên chạy trong môi trường Sage chứ chạy file Python import Sage đéo chạy được
### Solution 
```sage
from sage.all import * 
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib

def gen_shared_secret(P, n):
	S = n*P
	return S.xy()[0]

def decrypt_flag(iv_hex: str, ciphertext_hex: str, shared_secret: int) -> bytes:
    """
    Giải mã AES-CBC của flag từ IV, ciphertext (cả hai ở dạng hex) và shared secret (int).
    Trả về plaintext (FLAG) ở dạng bytes.
    """
    # Derive AES key từ shared_secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]

    # Chuyển IV và ciphertext từ hex về bytes
    iv = bytes.fromhex(iv_hex)
    ciphertext = bytes.fromhex(ciphertext_hex)

    # Giải mã và bỏ padding
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = (cipher.decrypt(ciphertext), AES.block_size)
    return plaintext

p = 1331169830894825846283645180581
a = -35
b = 98
E = EllipticCurve(GF(p), [a, b])
G = E(479691812266187139164535778017, 568535594075310466177352868412) 

#---------------------------------------------------------------------------------------------------
#               Đây là Thuật Toán MOV 
# Find embedding degree k
Gn = G.order()
k = 1
while p^k % Gn != 1:
    k += 1
print("Found k:", k)

# Select private key, and calculate public key Q
Q = E(1110072782478160369250829345256, 800079550745409318906383650948)

# Define new curve mod p^k and the points on it
Ek = EllipticCurve(GF(p ^ k), [a, b])
Gk = Ek(G)
Qk = Ek(Q)
Rk = Ek.random_point()

# Find a point T with order d such that d divides G's order
m = Rk.order()
d = gcd(m, Gn)
Tk = (m // d) * Rk
assert Tk.order() == d
assert (Gn*Tk).is_zero() # Point INFINITY

# Using T, pair G and Q to integers g and q such that q=g^n (mod p^k)
g = Gk.weil_pairing(Tk, Gn)
q = Qk.weil_pairing(Tk, Gn)
# Alternatively:
#g = Gk.tate_pairing(Tk, Gn, k)
#q = Qk.tate_pairing(Tk, Gn, k)

print("Calculating private key...")
found_key = q.log(g)
print((found_key)) 

#---------------------------------------------------------------------------------------------------

Qa = E(1110072782478160369250829345256 , 800079550745409318906383650948 )
Qb = E(1290982289093010194550717223760 , 762857612860564354370535420319 )
iv = 'eac58c26203c04f68d63dc2c58d79aca'
encrypted_flag = 'bb9ecbd3662d0671fd222ccb07e27b5500f304e3621a6f8e9c815bc8e4e6ee6ebc718ce9ca115cb4e41acb90dbcabb0d'

shared = gen_shared_secret(Qb, found_key) 
print(decrypt_flag(iv, encrypted_flag, shared))
```
Flag: crypto{MOV_attack_on_non_supersingular_curves}
    
# Signature 
    
Trong ECC cũng có hệ thống chữ ký giống RSA. Nó là 1 giá trị số đặc biệt giúp xác minh thông tin tin nhắn.
## Gen Signature 
Một Signature có cấu trúc $(r, s)$. 
    
Để tạo 1 Signature như vậy, ta có những bước như sau: 
- Tính toán 1 số ngẫu nhiên $k$, sau đó nhân nó với điểm sinh $G$ được 1 điểm $R = (x_R, y_R)$
- $n$ là bậc của đường Cong, $r = x_R \mod n$ chính là $r$ trong cấu trúc Signature. 
- Với $d_A$ là private key của người ký, ta sẽ tiến hành tính giá trị của $s$ theo công thức: 
$s = (e + r.d_A).k^{-1} \mod n$ với $e$ là giá trị hash của tin nhắn. 
## Digestive 
    
Đề bài này muốn ta khai thác lỗ hổng của thuật toán ký `NIST192p` và hàm `băm không băm`. 

- `NIST192p`: Chỉ ký vào 24 bytes đầu tiên. 
- Hàm băm của code không hề băm tí nào :v.
=> Nắm được điều này, ta chỉ cần dùng 1 username rất dài, khi `kí` và khi `verify`, ta chỉ làm việc với 24 byte đầu. 
=> Đồng thời, ta cũng đè thêm vào cuối file JSON gửi đi `admin : true`, khi hệ thống xác minh thông tin của trường `admin` nó sẽ lấy cái ở sau cùng.
### Solution 
```python
import requests
import json

# URL cơ sở của server CTF
BASE_URL = "https://web.cryptohack.org/digestive/" # Thay thế bằng URL thực tế của bài CTF nếu khác

def sign(username): 
    return requests.get(BASE_URL + '/sign/' + username).json()
def verify(msg, signature): 
    return requests.get(BASE_URL + '/verify/' + msg + '/' + signature).text

username = 'mosumsue'*100
sending = '{"admin": false, "username": "' + username + '", "admin": true }'
output = sign(username) 
print(output)
sig = (output["signature"])
flag = verify(sending, sig)
print(flag)
```
Flag: crypto{thanx_for_ctf_inspiration_https://mastodon.social/@filippo/109360453402691894}
## Curveball

Mấu chốt của bài tập này là muốn ta đưa vào sever 1 điểm sinh $G$ và 1 khóa riêng $d$ sao cho khi tính Public Key trùng với Public Key của `bing`. 
    
Để làm được thì cũng đơn giản thôi, ta chỉ cần lấy Public Key của `bing` rồi chia cho $d$ được ta chọn sẵn trong trường $n$ với $n$ là bậc của $G$. Sau đó ta lấy kết quả làm 1 điểm sinh rồi chỉ cần đưa nó cùng với $d$ lên sever là xong 
### Solution 
```python
from sage.all import *
import socket
import json
p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
a = -3
b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
Fp = GF(p)
E = EllipticCurve(Fp, [a, b])

q_bing_x = 0x3B827FF5E8EA151E6E51F8D0ABF08D90F571914A595891F9998A5BD49DFA3531
q_bing_y = 0xAB61705C502CA0F7AA127DEC096B2BBDC9BD3B4281808B3740C320810888592A

q_bing = E(q_bing_x, q_bing_y)
d = pow(2,-1,n) 
g = q_bing*d 
g_x, g_y = g.xy()


# 1. Điền thông tin
HOST = 'socket.cryptohack.org'  
PORT = 13382
# 2. Chuẩn bị payload
payload = {
    "private_key": 2,
    "host": "any",
    "curve": "secp256r1",
    "generator": [int(g_x), int(g_y)]
}

# 3. Gửi và nhận kết quả
try:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.recv(1024)  # Đọc và bỏ qua tin nhắn chào mừng
        
        # Gửi payload đã được mã hóa sang JSON và bytes
        s.sendall(json.dumps(payload).encode() + b'\n')
        
        # Đọc và in phản hồi
        response = s.recv(4096).decode()
        print(response.strip())

except Exception as e:
    print(f"Lỗi: {e}")
```
Flag: crypto{Curveballing_Microsoft_CVE-2020-0601}
## Montgomery's Ladder

Khi sử dụng ECC bình thường có thể ta sẽ gặp khả năng gặp phải những kiểu tấn công tinh vi như `timing` hay `LadderLeak` khiến cho hệ thống ECC HOÀN TOÀN SỤP ĐỔ. 
    
Thay vào đó, ta có thể triển khai ECC trên 1 đường cong khác đó là: ![image](https://hackmd.io/_uploads/SJ3TaDZQxl.png)

Các thuật toán: 
- ![image](https://hackmd.io/_uploads/ryqVRD-7lx.png)
- ![image](https://hackmd.io/_uploads/BJ1PADZmge.png)

Đề bài này đơn giản bắt ta tìm k.Q trong đường cong Montgomery. Ta biết được tọa độ x của Q, ta chỉ cần tìm y rồi sau đó nhân Q với k theo công thức là xong: 
```sage
# Bước 1: Thiết lập các hằng số và trường hữu hạn
# --------------------------------------------------
# p = 2^255 - 19
p = 2^255 - 19
F = GF(p)  # Tạo trường hữu hạn GF(p)

# Các hằng số của đường cong y^2 = x^3 + Ax^2 + x
A = 486662
B = 1 # Không cần thiết trong định nghĩa này nhưng có trong công thức tổng quát

# Bước 2: Định nghĩa đường cong Elliptic
# --------------------------------------------------
# Sage sử dụng dạng Weierstrass tổng quát: y^2 + a1*x*y + a3*y = x^3 + a2*x^2 + a4*x + a6
# So sánh với y^2 = x^3 + Ax^2 + x, ta có:
# a1=0, a3=0, a2=A, a4=1, a6=0
E = EllipticCurve(F, [0, A, 0, 1, 0])
print("Đã định nghĩa đường cong:", E)

# Bước 3: Định nghĩa điểm gốc P và khóa k
# --------------------------------------------------
x_p = F(9) # Tọa độ x của điểm P

# Tìm y_p bằng cách giải phương trình đường cong
# y_p^2 = x_p^3 + A*x_p^2 + x_p
y_p_squared = x_p^3 + A*x_p^2 + x_p
y_p = y_p_squared.sqrt() # Sage sẽ tự tìm căn bậc hai modulo p

# Tạo đối tượng điểm P
P = E(x_p, y_p)
print("Điểm gốc P:", P.xy())

# Khóa bí mật (vô hướng)
k = 0x1337c0decafe
print("Khóa k (thập phân):", k)

# Bước 4: Triển khai thuật toán Thang Montgomery
# --------------------------------------------------
# Chuyển k sang dạng nhị phân
k_bin = bin(k)[2:] # Lấy chuỗi nhị phân, bỏ đi tiền tố '0b'

# Khởi tạo R0, R1 theo thuật toán
R0 = P
R1 = 2*P  # Sage cho phép nhân đôi điểm dễ dàng như thế này!
print("Khởi tạo R0, R1 xong.")

# Lặp qua các bit của k, bắt đầu từ bit thứ hai (từ trái qua)
for i in range(1, len(k_bin)):
    bit = int(k_bin[i])
    
    if bit == 0:
        # R0' = 2*R0
        # R1' = R0 + R1
        R1 = R0 + R1 # Sage cho phép cộng điểm bằng toán tử '+'
        R0 = 2*R0
    else: # bit == 1
        # R0' = R0 + R1
        # R1' = 2*R1
        R0 = R0 + R1
        R1 = 2*R1

# Bước 5: Lấy kết quả
# --------------------------------------------------
# Sau vòng lặp, kết quả Q = [k]P chính là R0
Q = R0
x_q = Q.xy()[0] # Lấy tọa độ x của điểm Q

print("\n--- KẾT QUẢ ---")
print("Điểm Q = [k]P là:", Q.xy())
print("Tọa độ x của Q (dạng số của trường hữu hạn):", x_q)
print("Tọa độ x của Q (dạng số nguyên thập phân):")
print(int(x_q)) # In ra số nguyên để nộp đáp án
```
Flag: crypto{49231350462786016064336756977412654793383964726771892982507420921563002378152}