import nclib
import ecdsa
import hashlib
import binascii
from Crypto.Util.number import *

def _hex(x):
    return binascii.hexlify(x).decode()

def _hash(msg):
    return bytes_to_long(hashlib.sha1(msg).digest())

conn = nclib.Netcat(("crypto.csaw.io", 5002))
# conn = nclib.Netcat(("localhost", 1337))

N = 6
h = []
sgns = []
msgs = []

def get_messages(count):
    all_msgs = []

    conn.recv_until(b": ")
    conn.send_line(b"2")

    for i in range(count):
        conn.recv_until(f"Block {i}\n".encode())
        msg_line = conn.recv_line()
        print(msg_line)
        msg = binascii.unhexlify(msg_line.strip().split(b" ")[1])
        sig_line = conn.recv_line()
        sig_rs = sig_line.decode().split("(")[1].split(")")[0]
        r, s = sig_rs.split(", ")
        r = int(r)
        s = int(s)
        all_msgs.append((msg, r, s))

    return all_msgs

for i in range(N):
    msg = f"meow{i}".encode()
    msgs.append(msg)
    conn.recv_until(b": ")
    conn.send_line(b"1")
    conn.send_line(_hex(msg))

all_msgs = get_messages(N+1)

for i in range(N):
    (_, r, s) = all_msgs[i+1]
    prev_msg = all_msgs[i][0]
    real_hash = _hash(prev_msg + msgs[i])
    h.append(real_hash)
    sgns.append((r,s))

order=115792089237316195423570985008687907852837564279074904382605163141518161494337

s_inv = []
s = []
r = []
for i in range(N):
    s.append(sgns[i][1])
    r.append(sgns[i][0])
    s_inv.append(ecdsa.numbertheory.inverse_mod(s[i], order))

Z = GF(order)
R = PolynomialRing(Z, names=('dd',))
(dd,) = R._first_ngens(1)

# the polynomial we construct will have degree 1 + Sum_(i=1)^(i=N-3)i in dd
# our task here is to compute this polynomial in a constructive way starting from the N signatures in the given list order
# the generic formula will be given in terms of differences of nonces, i.e. k_ij = k_i - k_j where i and j are the signature indexes
# each k_ij is a first-degree polynomial in dd
# this function has the goal of returning it given i and j
def k_ij_poly(i, j):
	hi = Z(h[i])
	hj = Z(h[j])
	s_invi = Z(s_inv[i])
	s_invj = Z(s_inv[j])
	ri = Z(r[i])
	rj = Z(r[j])
	poly = dd*(ri*s_invi - rj*s_invj) + hi*s_invi - hj*s_invj
	return poly

# the idea is to compute the polynomial recursively from the given degree down to 0
# the algorithm is as follows:
# for 4 signatures the second degree polynomial is:
# k_12*k_12 - k_23*k_01
# so we can compute its coefficients.
# the polynomial for N signatures has degree 1 + Sum_(i=1)^(i=N-3)i and can be derived from the one for N-1 signatures

# let's define dpoly(i, j) recursively as the dpoly of degree i starting with index j

def dpoly(n, i, j):
	if i == 0:
		return (k_ij_poly(j+1, j+2))*(k_ij_poly(j+1, j+2)) - (k_ij_poly(j+2, j+3))*(k_ij_poly(j+0, j+1))
	else:
		left = dpoly(n, i-1, j)
		for m in range(1,i+2):
			left = left*(k_ij_poly(j+m, j+i+2))
		right = dpoly(n, i-1, j+1)
		for m in range(1,i+2):
			right = right*(k_ij_poly(j, j+m))
		return (left - right)

def print_dpoly(n, i, j):
	if i == 0:
		print('(k', j+1, j+2, '*k', j+1, j+2, '-k', j+2, j+3, '*k', j+0, j+1, ')', sep='', end='')
	else:
		print('(', sep='', end='')
		print_dpoly(n, i-1, j)
		for m in range(1,i+2):
			print('*k', j+m, j+i+2, sep='', end='')
		print('-', sep='', end='')
		print_dpoly(n, i-1, j+1)
		for m in range(1,i+2):
			print('*k', j, j+m, sep='', end='')
		print(')', sep='', end='')

print_dpoly(N-4, N-4, 0)
poly_target = dpoly(N-4, N-4, 0)
d_guesses = poly_target.roots()
print("results!!!!!\n\n\n")
print(d_guesses)

if len(d_guesses) == 1:
    conn.recv_until(b": ")
    conn.send_line(b"4")
    res = str(d_guesses[0][0]).encode()
    print("SENDING", res)
    conn.send_line(res)
    print(conn.recv_all(timeout=5))
