diff --git a/script/research/musig2/musig2.sage b/script/research/musig2/musig2.sage new file mode 100644 index 000000000..07d4f122d --- /dev/null +++ b/script/research/musig2/musig2.sage @@ -0,0 +1,91 @@ +# MuSig2: Simple Two-Round Schnorr Multi-Signatures +# https://eprint.iacr.org/2020/1261.pdf +# This scheme is n-of-n, not threshold. +from hashlib import sha256 + +# Nonces +v = 3 +# Participants +n = 5 + +# Pallas +p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 +q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 +Fp = GF(p) +Fq = GF(q) +Ep = EllipticCurve(Fp, (0, 5)) +Ep.set_order(q) + +# NullifierK Generator: g +nfk_x = 0x25e7aa169ca8198d2e375571faf4c9cf5e7eb192ccb5db9bd36f6aa7e447ca75 +nfk_y = 0x155c1f851b1a3384880473442008ff755fe0a49ec1c1b4332db8dce21ae001cc +g = Ep([nfk_x, nfk_y]) + +def hash_domain(domain, *args): + concat = domain.encode() + b"".join(str(arg).encode() for arg in args) + return Fq(int(sha256(concat).hexdigest(), 16)) + +# Domain separator for H_agg +def H_AGG(*args): + return hash_domain("musig2_H_agg", *args) + +# Domain separator for H_non +def H_NON(*args): + return hash_domain("musig2_H_non", *args) + +# Domain separator for H_sig +def H_SIG(*args): + return hash_domain("musig2_H_sig", *args) + +# ================= +# 1. Key generation +# ================= +x = [Fq.random_element() for _ in range(n)] +X = [x_i * g for x_i in x] + +# ================== +# 2. Key aggregation +# ================== +L = b"".join(str(X_i).encode() for X_i in X) +X_tilde = Fq(0) * g +for i in range(n): + a_i = H_AGG(L, X[i]) + X_tilde += X[i] * a_i + +# ====================== +# 3. First signing round +# ====================== +R_i = [] # Each participant's public nonces +r_i = [] # Each participant's secret nonces + +for _ in range(n): + r_j = [Fq.random_element() for _ in range(v)] + R_j = [r * g for r in r_j] + r_i.append(r_j) + R_i.append(R_j) + +# Sum up the nonces for all participants for each j +R = [sum(R_ij[j] for R_ij in R_i) for j in range(v)] +assert len(R) == v + +# ======================= +# 4. Second signing round +# ======================= +message = "Hello MuSig2" +s_i = [] +b = H_NON(X_tilde, *R, message) +R_total = sum(R[j] * b * (j+1) for j in range(v)) + +c = H_SIG(X_tilde, R_total, message) # Compute the challenge based on R_total +for i in range(n): + a_i = H_AGG(L, X[i]) + s_partial = c * a_i * x[i] + sum(r_i[i][j] * (b * (j+1)) for j in range(v)) + s_i.append(s_partial) + +s = sum(s_i) + +# =============== +# 5. Verification +# =============== +c = H_SIG(X_tilde, R_total, message) +assert g * s == R_total + X_tilde * c