frost.sage 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. # Two-Round Threshold Schnorr Signatures with FROST
  2. # https://datatracker.ietf.org/doc/pdf/draft-irtf-cfrg-frost-14
  3. import os
  4. from hashlib import sha256
  5. # Hacky way to import another sage module:
  6. os.system("sage --preparse frost_util.sage")
  7. os.system("mv frost_util.sage.py frost_util.py")
  8. from frost_util import *
  9. MAX_PARTICIPANTS = 10
  10. MIN_PARTICIPANTS = 4
  11. assert MIN_PARTICIPANTS <= MAX_PARTICIPANTS
  12. # Pallas
  13. p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
  14. q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
  15. Fp = GF(p)
  16. Fq = GF(q)
  17. Ep = EllipticCurve(Fp, (0, 5))
  18. Ep.set_order(q)
  19. # NullifierK Generator: G
  20. nfk_x = 0x25e7aa169ca8198d2e375571faf4c9cf5e7eb192ccb5db9bd36f6aa7e447ca75
  21. nfk_y = 0x155c1f851b1a3384880473442008ff755fe0a49ec1c1b4332db8dce21ae001cc
  22. G = Ep([nfk_x, nfk_y])
  23. # Secret key to share, we assume this is key distribution with trusted dealer.
  24. sk = Fq.random_element()
  25. group_pk = sk * G
  26. alpha = [sk]
  27. for i in range(MIN_PARTICIPANTS-1):
  28. alpha.append(Fq.random_element())
  29. R.<ω> = PolynomialRing(Fq)
  30. poly = R(alpha)
  31. assert poly.degree() == MIN_PARTICIPANTS-1
  32. assert poly.coefficients()[0] == sk
  33. # Secret key shares
  34. sk_i = [poly(i) for i in range(1, MAX_PARTICIPANTS+1)]
  35. # ======================
  36. # Round One - Commitment
  37. # ======================
  38. # Round one involves each participant generating nonces and their
  39. # corresponding public commitments. A nonce is a pair of Scalar
  40. # values, and a commitment is a pair of elliptic curve points.
  41. # Each participant's behaviour in this round is described by the
  42. # commit function below. Note that this function invokes nonce_generate
  43. # twice, once for each type of nonce produced. The output of this
  44. # function is a pair of secret nonces (hiding_nonce, binding_nonce)
  45. # and their corresponding public commitments (hiding_nonce_commitment,
  46. # binding_nonce_commitment).
  47. # Inputs:
  48. # - secret, a Scalar
  49. # Outputs:
  50. # - nonce, a Scalar
  51. def nonce_generate(secret):
  52. random_bytes = os.urandom(32)
  53. return Fq(H3(random_bytes, secret))
  54. # Inputs:
  55. # - sk_i, the secret key share, a Scalar
  56. # Outputs:
  57. # - (nonce, comm), a tuple of nonce and nonce commitment pairs,
  58. # where each value in the nonce pair is a Scalar and each value
  59. # in the nonce commitment pair is an elliptic curve point
  60. def commit(sk_i):
  61. hiding_nonce = nonce_generate(sk_i)
  62. binding_nonce = nonce_generate(sk_i)
  63. hiding_nonce_commit = hiding_nonce * G
  64. binding_nonce_commit = binding_nonce * G
  65. nonces = (hiding_nonce, binding_nonce)
  66. commits = (hiding_nonce_commit, binding_nonce_commit)
  67. return (nonces, commits)
  68. P_nonces = []
  69. P_commits = []
  70. # Either all participants or just the threshold should create nonces.
  71. # It is only important that commit_list has MIN/NUM_PARTICIPANTS.
  72. for i in range(MAX_PARTICIPANTS):
  73. nonces, commits = commit(sk_i[i])
  74. P_nonces.append(nonces)
  75. P_commits.append(commits)
  76. commit_list = []
  77. for (i, (hnc, bnc)) in enumerate(P_commits[:MIN_PARTICIPANTS]):
  78. commit_list.append((Fq(i+1), hnc, bnc))
  79. # The outputs nonce and comm from participant P_i should both be stored
  80. # locally and kept for use in the second round. The nonce value is secret
  81. # and MUST NOT be shared, whereas the public output comm is sent to the
  82. # Coordinator. The nonce values produced by this function MUST NOT be used
  83. # in more than one invocation of `sign()`, and the nonces MUST be generated
  84. # from a source of secure randomness.
  85. # ======================================
  86. # Round Two - Signature Share Generation
  87. # ======================================
  88. # In round two, the Coordinator is responsible for sending the message to
  89. # be signed, and for choosing which participants will participate (of number
  90. # at least MIN_PARTICIPANTS). Signers additionally require locally held data;
  91. # specifically, their secret key and the nonces corresponding to their
  92. # commitment issued in round one.
  93. # The Coordinator begins by sending each participant the message to be
  94. # signed along with the set of signing commitments for all participants
  95. # in the participant list.
  96. # Inputs:
  97. # - group_pk, the public key corresponding to the group signing key
  98. # - commit_list = [(i, hiding_nonce_commit_i, binding_nonce_commit_i), ...],
  99. # a list of commitments issued by each participant, where each element
  100. # indicates a nonzero Scalar identifier i and two commitments which are
  101. # elliptic curve points. This list MUST be sorted in ascending order by
  102. # identifier.
  103. # - msg, the message to be signed.
  104. # Outputs:
  105. # - binding_factor_list, a list of (nonzero Scalar, Scalar) tuples
  106. # representing the binding factors
  107. def compute_binding_factors(group_pk, commit_list, msg):
  108. msg_hash = Fq(H4(msg))
  109. encoded_commitment_hash = Fq(H5(encode_group_commitment_list(commit_list)))
  110. rho_input_prefix = b"".join([
  111. point_to_bytes(group_pk),
  112. scalar_to_bytes(msg_hash),
  113. scalar_to_bytes(encoded_commitment_hash),
  114. ])
  115. binding_factor_list = []
  116. for (ident, hiding_nonce_commit, binding_nonce_commit) in commit_list:
  117. rho_input = b"".join([rho_input_prefix, scalar_to_bytes(ident)])
  118. binding_factor = Fq(H1(rho_input))
  119. binding_factor_list.append((ident, binding_factor))
  120. return binding_factor_list
  121. def binding_factor_for_participant(binding_factor_list, ident):
  122. for (i, binding_factor) in binding_factor_list:
  123. if ident == i:
  124. return binding_factor
  125. raise "invalid participant"
  126. def compute_group_commitment(commit_list, binding_factor_list):
  127. group_commitment = Ep(0)
  128. for (ident, hiding_nonce_commit, binding_nonce_commit) in commit_list:
  129. binding_factor = binding_factor_for_participant(binding_factor_list, ident)
  130. binding_nonce = binding_nonce_commit * binding_factor
  131. group_commitment += hiding_nonce_commit + binding_nonce
  132. return group_commitment
  133. def participants_from_commitment_list(commit_list):
  134. identifiers = []
  135. for (identifier, _, _) in commit_list:
  136. identifiers.append(identifier)
  137. return identifiers
  138. def derive_interpolating_value(L, x_i):
  139. if x_i not in L:
  140. raise "invalid parameters"
  141. for x_j in L:
  142. if L.count(x_j) > 1:
  143. raise "invalid parameters"
  144. numerator = Fq(1)
  145. denominator = Fq(1)
  146. for x_j in L:
  147. if x_j == x_i:
  148. continue
  149. numerator *= x_j
  150. denominator *= x_j - x_i
  151. value = numerator / denominator
  152. return value
  153. def compute_challenge(group_commitment, group_pk, msg):
  154. challenge_input = b"".join([
  155. point_to_bytes(group_commitment),
  156. point_to_bytes(group_pk),
  157. msg,
  158. ])
  159. challenge = Fq(H2(challenge_input))
  160. return challenge
  161. def sign(ident, sk_i, group_pk, nonce_i, msg, commit_list):
  162. # Compute the binding factor(s)
  163. binding_factor_list = compute_binding_factors(group_pk, commit_list, msg)
  164. binding_factor = binding_factor_for_participant(binding_factor_list, ident)
  165. # Compute the group commitment
  166. group_commit = compute_group_commitment(commit_list, binding_factor_list)
  167. # Compute the interpolating value
  168. participant_list = participants_from_commitment_list(commit_list)
  169. lambda_i = derive_interpolating_value(participant_list, ident)
  170. # Compute the per-message challenge
  171. challenge = compute_challenge(group_commit, group_pk, msg)
  172. # Compute the signature share
  173. (hiding_nonce, binding_nonce) = nonce_i
  174. sig_share = hiding_nonce + (binding_nonce * binding_factor) + \
  175. (lambda_i * sk_i * challenge)
  176. return sig_share
  177. # For demo purposes, we'll just pick the first participants in order.
  178. msg = b"Hello FROST"
  179. sig_shares = []
  180. for i in range(MIN_PARTICIPANTS):
  181. sig_share = sign(Fq(i+1), sk_i[i], group_pk, P_nonces[i], msg, commit_list)
  182. sig_shares.append(sig_share)
  183. # ===========================
  184. # Signature Share Aggregation
  185. # ===========================
  186. def verify_signature_share(ident, PK_i, comm_i, sig_share_i, commit_list,
  187. group_pk, msg):
  188. binding_factor_list = compute_binding_factors(group_pk, commit_list, msg)
  189. binding_factor = binding_factor_for_participant(binding_factor_list, ident)
  190. group_commit = compute_group_commitment(commit_list, binding_factor_list)
  191. (hiding_nonce_comm, binding_nonce_comm) = comm_i
  192. comm_share = hiding_nonce_comm + binding_nonce_comm * binding_factor
  193. challenge = compute_challenge(group_commit, group_pk, msg)
  194. participant_list = participants_from_commitment_list(commit_list)
  195. lambda_i = derive_interpolating_value(participant_list, ident)
  196. l = sig_share_i * G
  197. r = comm_share + PK_i * (challenge * lambda_i)
  198. return l == r
  199. # Verify individual signature shares:
  200. for i in range(MIN_PARTICIPANTS):
  201. assert verify_signature_share(Fq(i+1), sk_i[i] * G, P_commits[i],
  202. sig_shares[i], commit_list, group_pk, msg)
  203. def aggregate(commit_list, msg, group_pk, sig_shares):
  204. binding_factor_list = compute_binding_factors(group_pk, commit_list, msg)
  205. group_commit = compute_group_commitment(commit_list, binding_factor_list)
  206. # Compute aggregated signature
  207. z = Fq(0)
  208. for z_i in sig_shares:
  209. z += z_i
  210. return (group_commit, z)
  211. group_commit, z = aggregate(commit_list, msg, group_pk, sig_shares)
  212. # ============
  213. # Verification
  214. # ============
  215. c = compute_challenge(group_commit, group_pk, msg)
  216. assert G * z == group_commit + group_pk * c