# Publicly Verifiable Secret Sharing
# https://www.win.tue.nl/~berry/papers/crypto99.pdf
# This scheme depends on an honest dealer.
from random import sample
from hashlib import sha256
from itertools import chain

t = 3  # Threshold
n = 5  # Participants

# Pallas
p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
Fp = GF(p)
Fq = GF(q)
Ep = EllipticCurve(Fp, (0, 5))
Ep.set_order(q * 0x01)

# ValueCommitR Generator: g
vcr_x = 0x07f444550fa409bb4f66235bea8d2048406ed745ee90802f0ec3c668883c5a91
vcr_y = 0x24136777af26628c21562cc9e46fb7c2279229f1f39281460e2f46c8a772d9ca
g = Ep([vcr_x, vcr_y])

# NullifierK Generator: G
nfk_x = 0x25e7aa169ca8198d2e375571faf4c9cf5e7eb192ccb5db9bd36f6aa7e447ca75
nfk_y = 0x155c1f851b1a3384880473442008ff755fe0a49ec1c1b4332db8dce21ae001cc
G = Ep([nfk_x, nfk_y])

# ==============
# Initialization
# ==============
# The participants create their keypairs and register their public keys
# y_i = G^{x_i}
x = []
y = []
for i in range(n):
    x_i = Fq.random_element()
    x.append(x_i)
    y.append(G * x_i)

# ============
# Distribution
# ============
# The dealer selects a secret s:
s = Fq.random_element()

# The dealer picks a random polynomial p of degree at most t-1 with
# coefficients in Fq and sets s=alpha_0
alpha = [s]
for i in range(t-1):
    alpha.append(Fq.random_element())
R.<ω> = PolynomialRing(Fq)
p = R(alpha)
assert p.degree() == t-1
assert p.coefficients()[0] == s

# The dealer keeps this polynomial secret but publishes the related
# commitments C_j = g ^ {a_j} , for 0 ≤ j < t
C = []
for j in range(t):
    C.append(g * alpha[j])

# The dealer also publishes the encrypted shares Y_i = y_i ^ p(i),
# for 1 ≤ i ≤ n , using the public keys of the participants:
Y = []
for i in range(1, n+1):
    Y.append(y[i-1] * p(i))

# Let X_i = prod_{j=0}^{t-1} C_j * i^j. The verifier computes this from the
# published commitments C_j.
X = []
for i in range(1, n+1):
    X_i = Ep(0)
    for j in range(t):
        X_i += C[j] * (i^j)
    X.append(X_i)

# The dealer shows that the encrypted shares are consistent by
# producing a proof of knowledge of the unique p(i), 1 ≤ i ≤ n,
# satisfying: X_i = g^p(i) , Y_i = y_i^p(i)
for i in range(1, n+1):
    assert X[i-1] == g * p(i)
    assert Y[i-1] == y[i-1] * p(i)

# For a non-interactive proof, we can use the Fiat-Shamir technique.
# (See DLEQ in the paper)
# The prover calculates a1_i and a2_i:
w_i = []
p_a1 = []
p_a2 = []
for i in range(n):
    w = Fq.random_element()
    w_i.append(w)
    a1_i = g * w
    a2_i = y[i] * w
    p_a1.append(a1_i)
    p_a2.append(a2_i)

# And then hashes the necessary values in order to produce c:
assert len(X) == len(Y) == len(p_a1) == len(p_a2)
c_hasher = sha256()
for point in chain(X, Y, p_a1, p_a2):
    x_coord, y_coord = point.xy()
    c_hasher.update(str(x_coord).encode())
    c_hasher.update(str(y_coord).encode())

# Prover publishes c
c = Fq(int(c_hasher.hexdigest(), 16))

# And finally, prover calculates and publishes r_i responses:
r = []
for i in range(1, n+1):
    r_i = w_i[i-1] - p(i) * c
    r.append(r_i)

# The verifier calculates a1_i and a2_i:
# a1_i = g^r_i * X_i^c
# a2_i = y_i^r_i * Y_i^c
v_a1 = []
v_a2 = []
for i in range(n):
    a1_i = (g * r[i]) + (X[i] * c)
    a2_i = (y[i] * r[i]) + (Y[i] * c)
    v_a1.append(a1_i)
    v_a2.append(a2_i)

# And then hashes the necessary values in order to produce c:
v_hasher = sha256()
for point in chain(X, Y, v_a1, v_a2):
    x_coord, y_coord = point.xy()
    v_hasher.update(str(x_coord).encode())
    v_hasher.update(str(y_coord).encode())

v_c = Fq(int(v_hasher.hexdigest(), 16))

# And checks that the hash matches the published c
assert v_c == c

# ==============
# Reconstruction
# ==============
# Using its private key x_i, each participant finds the share S_i = G^p(i)
# from Y_i by computing S_i = Y_i^(1/x_i). They publish S_i plus a proof
# that the value S_i is a correct decryption of Y_i. To this end it
# suffices to prove knowledge of an alpha such that y_i = G^alpha and
# Y_i = S_i^alpha, which is accomplished by the non-interactive version
# of the protocol DLEQ(G, y_i, S_i, Y_i).
S = []
for i in range(n):
    S_i = Y[i] * x[i].inverse_of_unit()
    assert S_i == G * p(i+1)
    S.append(S_i)


# DLEQ proofs for reconstruction
dleq_proofs = []
for i in range(n):
    w = Fq.random_element()
    a1 = G * w
    a2 = S[i] * w

    dleq_hasher = sha256()
    for point in [G, y[i], S[i], Y[i], a1, a2]:
        x_coord, y_coord = point.xy()
        dleq_hasher.update(str(x_coord).encode())
        dleq_hasher.update(str(y_coord).encode())

    c = Fq(int(dleq_hasher.hexdigest(), 16))
    r = w - x[i] * c
    dleq_proofs.append((c, r))

# DLEQ verifications for reconstruction
for i in range(n):
    c, r = dleq_proofs[i]
    a1 = G * r + y[i] * c
    a2 = S[i] * r + Y[i] * c

    dleq_hasher = sha256()
    for point in [G, y[i], S[i], Y[i], a1, a2]:
        x_coord, y_coord = point.xy()
        dleq_hasher.update(str(x_coord).encode())
        dleq_hasher.update(str(y_coord).encode())

    v_c = Fq(int(dleq_hasher.hexdigest(), 16))
    assert v_c == c

# Pooling the shares. Sample a set of t shares and reconstruct secret.
sample_indices = sorted(sample(range(n), t))
sampled_shares = [S[i] for i in sample_indices]
pooled = Ep(0)

def lambda_func(i, t, indices):
    lambda_i = Fq(1)
    for j in indices:
        if j != i:
            lambda_i *= Fq(j+1) / (Fq(i+1) - Fq(j+1))
    return lambda_i

for idx, share in zip(sample_indices, sampled_shares):
    pooled += share * lambda_func(idx, t, sample_indices)

# Assert reconstructed secret
assert G*s == G*p(0) == pooled
