From 4bfd864f10b68b71482b35c818559068ef8d5797 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Wed, 27 Nov 2024 20:54:24 +0100 Subject: doc: Add RFC documents --- doc/rfc/rfc9591.txt | 2436 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2436 insertions(+) create mode 100644 doc/rfc/rfc9591.txt (limited to 'doc/rfc/rfc9591.txt') diff --git a/doc/rfc/rfc9591.txt b/doc/rfc/rfc9591.txt new file mode 100644 index 0000000..0cb2058 --- /dev/null +++ b/doc/rfc/rfc9591.txt @@ -0,0 +1,2436 @@ + + + + +Internet Research Task Force (IRTF) D. Connolly +Request for Comments: 9591 Zcash Foundation +Category: Informational C. Komlo +ISSN: 2070-1721 University of Waterloo, Zcash Foundation + I. Goldberg + University of Waterloo + C. A. Wood + Cloudflare + June 2024 + + + The Flexible Round-Optimized Schnorr Threshold (FROST) Protocol for + Two-Round Schnorr Signatures + +Abstract + + This document specifies the Flexible Round-Optimized Schnorr + Threshold (FROST) signing protocol. FROST signatures can be issued + after a threshold number of entities cooperate to compute a + signature, allowing for improved distribution of trust and redundancy + with respect to a secret key. FROST depends only on a prime-order + group and cryptographic hash function. This document specifies a + number of ciphersuites to instantiate FROST using different prime- + order groups and hash functions. This document is a product of the + Crypto Forum Research Group (CFRG) in the IRTF. + +Status of This Memo + + This document is not an Internet Standards Track specification; it is + published for informational purposes. + + This document is a product of the Internet Research Task Force + (IRTF). The IRTF publishes the results of Internet-related research + and development activities. These results might not be suitable for + deployment. This RFC represents the consensus of the Crypto Forum + Research Group of the Internet Research Task Force (IRTF). Documents + approved for publication by the IRSG are not candidates for any level + of Internet Standard; see Section 2 of RFC 7841. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + https://www.rfc-editor.org/info/rfc9591. + +Copyright Notice + + Copyright (c) 2024 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. + +Table of Contents + + 1. Introduction + 2. Conventions and Definitions + 3. Cryptographic Dependencies + 3.1. Prime-Order Group + 3.2. Cryptographic Hash Function + 4. Helper Functions + 4.1. Nonce Generation + 4.2. Polynomials + 4.3. List Operations + 4.4. Binding Factors Computation + 4.5. Group Commitment Computation + 4.6. Signature Challenge Computation + 5. Two-Round FROST Signing Protocol + 5.1. Round One - Commitment + 5.2. Round Two - Signature Share Generation + 5.3. Signature Share Aggregation + 5.4. Identifiable Abort + 6. Ciphersuites + 6.1. FROST(Ed25519, SHA-512) + 6.2. FROST(ristretto255, SHA-512) + 6.3. FROST(Ed448, SHAKE256) + 6.4. FROST(P-256, SHA-256) + 6.5. FROST(secp256k1, SHA-256) + 6.6. Ciphersuite Requirements + 7. Security Considerations + 7.1. Side-Channel Mitigations + 7.2. Optimizations + 7.3. Nonce Reuse Attacks + 7.4. Protocol Failures + 7.5. Removing the Coordinator Role + 7.6. Input Message Hashing + 7.7. Input Message Validation + 8. IANA Considerations + 9. References + 9.1. Normative References + 9.2. Informative References + Appendix A. Schnorr Signature Encoding + Appendix B. Schnorr Signature Generation and Verification for + Prime-Order Groups + Appendix C. Trusted Dealer Key Generation + C.1. Shamir Secret Sharing + C.1.1. Additional Polynomial Operations + C.2. Verifiable Secret Sharing + Appendix D. Random Scalar Generation + D.1. Rejection Sampling + D.2. Wide Reduction + Appendix E. Test Vectors + E.1. FROST(Ed25519, SHA-512) + E.2. FROST(Ed448, SHAKE256) + E.3. FROST(ristretto255, SHA-512) + E.4. FROST(P-256, SHA-256) + E.5. FROST(secp256k1, SHA-256) + Acknowledgments + Authors' Addresses + +1. Introduction + + Unlike signatures in a single-party setting, threshold signatures + require cooperation among a threshold number of signing participants, + each holding a share of a common private key. The security of + threshold schemes in general assumes that an adversary can corrupt + strictly fewer than a threshold number of signer participants. + + This document specifies the Flexible Round-Optimized Schnorr + Threshold (FROST) signing protocol based on the original work in + [FROST20]. FROST reduces network overhead during threshold signing + operations while employing a novel technique to protect against + forgery attacks applicable to prior Schnorr-based threshold signature + constructions. FROST requires two rounds to compute a signature. + Single-round signing variants based on [FROST20] are out of scope. + + FROST depends only on a prime-order group and cryptographic hash + function. This document specifies a number of ciphersuites to + instantiate FROST using different prime-order groups and hash + functions. Two ciphersuites can be used to produce signatures that + are compatible with Edwards-Curve Digital Signature Algorithm (EdDSA) + variants Ed25519 and Ed448 as specified in [RFC8032], i.e., the + signatures can be verified with a verifier that is compliant with + [RFC8032]. However, unlike EdDSA, the signatures produced by FROST + are not deterministic, since deriving nonces deterministically allows + for a complete key-recovery attack in multi-party, discrete + logarithm-based signatures. + + Key generation for FROST signing is out of scope for this document. + However, for completeness, key generation with a trusted dealer is + specified in Appendix C. + + This document represents the consensus of the Crypto Forum Research + Group (CFRG). It is not an IETF product and is not a standard. + +2. Conventions and Definitions + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and + "OPTIONAL" in this document are to be interpreted as described in + BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all + capitals, as shown here. + + The following notation is used throughout the document. + + byte: A sequence of eight bits. + + random_bytes(n): Outputs n bytes, sampled uniformly at random using + a cryptographically secure pseudorandom number generator (CSPRNG). + + count(i, L): Outputs the number of times the element i is + represented in the list L. + + len(l): Outputs the length of list l, e.g., len([1,2,3]) = 3. + + reverse(l): Outputs the list l in reverse order, e.g., + reverse([1,2,3]) = [3,2,1]. + + range(a, b): Outputs a list of integers from a to b-1 in ascending + order, e.g., range(1, 4) = [1,2,3]. + + pow(a, b): Outputs the result, a Scalar, of a to the power of b, + e.g., pow(2, 3) = 8 modulo the relevant group order p. + + ||: Denotes concatenation of byte strings, i.e., x || y denotes the + byte string x, immediately followed by the byte string y, with no + extra separator, yielding xy. + + nil: Denotes an empty byte string. + + Unless otherwise stated, we assume that secrets are sampled uniformly + at random using a CSPRNG; see [RFC4086] for additional guidance on + the generation of random numbers. + +3. Cryptographic Dependencies + + FROST signing depends on the following cryptographic constructs: + + * Prime-order group (Section 3.1) + + * Cryptographic hash function (Section 3.2) + + The following sections describe these constructs in more detail. + +3.1. Prime-Order Group + + FROST depends on an abelian group of prime order p. We represent + this group as the object G that additionally defines helper functions + described below. The group operation for G is addition + with + identity element I. For any elements A and B of the group G, A + B = + B + A is also a member of G. Also, for any A in G, there exists an + element -A such that A + (-A) = (-A) + A = I. For convenience, we + use - to denote subtraction, e.g., A - B = A + (-B). Integers, taken + modulo the group order p, are called "Scalars"; arithmetic operations + on Scalars are implicitly performed modulo p. Since p is prime, + Scalars form a finite field. Scalar multiplication is equivalent to + the repeated application of the group operation on an element A with + itself r-1 times, denoted as ScalarMult(A, r). We denote the sum, + difference, and product of two Scalars using the +, -, and * + operators, respectively. (Note that this means + may refer to group + element addition or Scalar addition, depending on the type of the + operands.) For any element A, ScalarMult(A, p) = I. We denote B as + a fixed generator of the group. Scalar base multiplication is + equivalent to the repeated application of the group operation on B + with itself r-1 times, denoted as ScalarBaseMult(r). The set of + Scalars corresponds to GF(p), which we refer to as the Scalar field. + It is assumed that group element addition, negation, and equality + comparison can be efficiently computed for arbitrary group elements. + + This document uses types Element and Scalar to denote elements of the + group G and its set of Scalars, respectively. We denote Scalar(x) as + the conversion of integer input x to the corresponding Scalar value + with the same numeric value. For example, Scalar(1) yields a Scalar + representing the value 1. Moreover, we use the type NonZeroScalar to + denote a Scalar value that is not equal to zero, i.e., Scalar(0). We + denote equality comparison of these types as == and assignment of + values by =. When comparing Scalar values, e.g., for the purposes of + sorting lists of Scalar values, the least nonnegative representation + mod p is used. + + We now detail a number of member functions that can be invoked on G. + + Order(): Outputs the order of G (i.e., p). + + Identity(): Outputs the identity Element of the group (i.e., I). + + RandomScalar(): Outputs a random Scalar element in GF(p), i.e., a + random Scalar in [0, p - 1]. + + ScalarMult(A, k): Outputs the Scalar multiplication between Element + A and Scalar k. + + ScalarBaseMult(k): Outputs the Scalar multiplication between Scalar + k and the group generator B. + + SerializeElement(A): Maps an Element A to a canonical byte array buf + of fixed length Ne. This function raises an error if A is the + identity element of the group. + + DeserializeElement(buf): Attempts to map a byte array buf to an + Element A and fails if the input is not the valid canonical byte + representation of an element of the group. This function raises + an error if deserialization fails or if A is the identity element + of the group; see Section 6 for group-specific input validation + steps. + + SerializeScalar(s): Maps a Scalar s to a canonical byte array buf of + fixed length Ns. + + DeserializeScalar(buf): Attempts to map a byte array buf to a Scalar + s. This function raises an error if deserialization fails; see + Section 6 for group-specific input validation steps. + +3.2. Cryptographic Hash Function + + FROST requires the use of a cryptographically secure hash function, + generically written as H, which is modeled as a random oracle in + security proofs for the protocol (see [FROST20] and [StrongerSec22]). + For concrete recommendations on hash functions that SHOULD be used in + practice, see Section 6. Using H, we introduce distinct domain- + separated hashes H1, H2, H3, H4, and H5: + + * H1, H2, and H3 map arbitrary byte strings to Scalar elements + associated with the prime-order group. + + * H4 and H5 are aliases for H with distinct domain separators. + + The details of H1, H2, H3, H4, and H5 vary based on the ciphersuite + used. See Section 6 for more details about each. + +4. Helper Functions + + Beyond the core dependencies, the protocol in this document depends + on the following helper operations: + + * Nonce generation (Section 4.1); + + * Polynomials (Section 4.2); + + * List operations (Section 4.3); + + * Binding factors computation (Section 4.4); + + * Group commitment computation (Section 4.5); and + + * Signature challenge computation (Section 4.6). + + The following sections describe these operations in more detail. + +4.1. Nonce Generation + + To hedge against a bad random number generator (RNG) that outputs + predictable values, nonces are generated with the nonce_generate + function by combining fresh randomness with the secret key as input + to a domain-separated hash function built from the ciphersuite hash + function H. This domain-separated hash function is denoted as H3. + This function always samples 32 bytes of fresh randomness to ensure + that the probability of nonce reuse is at most 2^-128 as long as no + more than 2^64 signatures are computed by a given signing + participant. + + Inputs: + - secret, a Scalar. + + Outputs: + - nonce, a Scalar. + + def nonce_generate(secret): + random_bytes = random_bytes(32) + secret_enc = G.SerializeScalar(secret) + return H3(random_bytes || secret_enc) + +4.2. Polynomials + + This section defines polynomials over Scalars that are used in the + main protocol. A polynomial of maximum degree t is represented as a + list of t+1 coefficients, where the constant term of the polynomial + is in the first position and the highest-degree coefficient is in the + last position. For example, the polynomial x^2 + 2x + 3 has degree 2 + and is represented as a list of three coefficients [3, 2, 1]. A + point on the polynomial f is a tuple (x, y), where y = f(x). + + The function derive_interpolating_value derives a value that is used + for polynomial interpolation. It is provided a list of x-coordinates + as input, each of which cannot equal 0. + + Inputs: + - L, the list of x-coordinates, each a NonZeroScalar. + - x_i, an x-coordinate contained in L, a NonZeroScalar. + + Outputs: + - value, a Scalar. + + Errors: + - "invalid parameters", if 1) x_i is not in L, or if 2) any + x-coordinate is represented more than once in L. + + def derive_interpolating_value(L, x_i): + if x_i not in L: + raise "invalid parameters" + for x_j in L: + if count(x_j, L) > 1: + raise "invalid parameters" + + numerator = Scalar(1) + denominator = Scalar(1) + for x_j in L: + if x_j == x_i: continue + numerator *= x_j + denominator *= x_j - x_i + + value = numerator / denominator + return value + +4.3. List Operations + + This section describes helper functions that work on lists of values + produced during the FROST protocol. The following function encodes a + list of participant commitments into a byte string for use in the + FROST protocol. + + Inputs: + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + + Outputs: + - encoded_group_commitment, the serialized representation of + commitment_list, a byte string. + + def encode_group_commitment_list(commitment_list): + encoded_group_commitment = nil + for (identifier, hiding_nonce_commitment, + binding_nonce_commitment) in commitment_list: + encoded_commitment = ( + G.SerializeScalar(identifier) || + G.SerializeElement(hiding_nonce_commitment) || + G.SerializeElement(binding_nonce_commitment)) + encoded_group_commitment = ( + encoded_group_commitment || + encoded_commitment) + return encoded_group_commitment + + The following function is used to extract identifiers from a + commitment list. + + Inputs: + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + + Outputs: + - identifiers, a list of NonZeroScalar values. + + def participants_from_commitment_list(commitment_list): + identifiers = [] + for (identifier, _, _) in commitment_list: + identifiers.append(identifier) + return identifiers + + The following function is used to extract a binding factor from a + list of binding factors. + + Inputs: + - binding_factor_list = [(i, binding_factor), ...], + a list of binding factors for each participant, where each element + in the list indicates a NonZeroScalar identifier i and Scalar + binding factor. + - identifier, participant identifier, a NonZeroScalar. + + Outputs: + - binding_factor, a Scalar. + + Errors: + - "invalid participant", when the designated participant is + not known. + + def binding_factor_for_participant(binding_factor_list, identifier): + for (i, binding_factor) in binding_factor_list: + if identifier == i: + return binding_factor + raise "invalid participant" + +4.4. Binding Factors Computation + + This section describes the subroutine for computing binding factors + based on the participant commitment list, message to be signed, and + group public key. + + Inputs: + - group_public_key, the public key corresponding to the group signing + key, an Element. + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + - msg, the message to be signed. + + Outputs: + - binding_factor_list, a list of (NonZeroScalar, Scalar) tuples + representing the binding factors. + + def compute_binding_factors(group_public_key, commitment_list, msg): + group_public_key_enc = G.SerializeElement(group_public_key) + // Hashed to a fixed length. + msg_hash = H4(msg) + // Hashed to a fixed length. + encoded_commitment_hash = + H5(encode_group_commitment_list(commitment_list)) + // The encoding of the group public key is a fixed length + // within a ciphersuite. + rho_input_prefix = group_public_key_enc || msg_hash || + encoded_commitment_hash + + binding_factor_list = [] + for (identifier, hiding_nonce_commitment, + binding_nonce_commitment) in commitment_list: + rho_input = rho_input_prefix || G.SerializeScalar(identifier) + binding_factor = H1(rho_input) + binding_factor_list.append((identifier, binding_factor)) + return binding_factor_list + +4.5. Group Commitment Computation + + This section describes the subroutine for creating the group + commitment from a commitment list. + + Inputs: + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + - binding_factor_list = [(i, binding_factor), ...], + a list of (NonZeroScalar, Scalar) tuples representing the binding + factor Scalar for the given identifier. + + Outputs: + - group_commitment, an Element. + + def compute_group_commitment(commitment_list, binding_factor_list): + group_commitment = G.Identity() + for (identifier, hiding_nonce_commitment, + binding_nonce_commitment) in commitment_list: + binding_factor = binding_factor_for_participant( + binding_factor_list, identifier) + binding_nonce = G.ScalarMult( + binding_nonce_commitment, + binding_factor) + group_commitment = ( + group_commitment + + hiding_nonce_commitment + + binding_nonce) + return group_commitment + + Note that the performance of this algorithm is defined naively and + scales linearly relative to the number of signers. For improved + performance, the group commitment can be computed using multi- + exponentiation techniques such as Pippinger's algorithm; see + [MultExp] for more details. + +4.6. Signature Challenge Computation + + This section describes the subroutine for creating the per-message + challenge. + + Inputs: + - group_commitment, the group commitment, an Element. + - group_public_key, the public key corresponding to the group signing + key, an Element. + - msg, the message to be signed, a byte string. + + Outputs: + - challenge, a Scalar. + + def compute_challenge(group_commitment, group_public_key, msg): + group_comm_enc = G.SerializeElement(group_commitment) + group_public_key_enc = G.SerializeElement(group_public_key) + challenge_input = group_comm_enc || group_public_key_enc || msg + challenge = H2(challenge_input) + return challenge + +5. Two-Round FROST Signing Protocol + + This section describes the two-round FROST signing protocol for + producing Schnorr signatures. The protocol is configured to run with + a selection of NUM_PARTICIPANTS signer participants and a + Coordinator. NUM_PARTICIPANTS is a positive and non-zero integer + that MUST be at least MIN_PARTICIPANTS, but MUST NOT be larger than + MAX_PARTICIPANTS, where MIN_PARTICIPANTS <= MAX_PARTICIPANTS and + MIN_PARTICIPANTS is a positive and non-zero integer. Additionally, + MAX_PARTICIPANTS MUST be a positive integer less than the group + order. A signer participant, or simply "participant", is an entity + that is trusted to hold and use a signing key share. The Coordinator + is an entity with the following responsibilities: + + 1. Determining the participants that will participate (at least + MIN_PARTICIPANTS in number); + + 2. Coordinating rounds (receiving and forwarding inputs among + participants); + + 3. Aggregating signature shares output by each participant; and + + 4. Publishing the resulting signature. + + FROST assumes that the Coordinator and the set of signer participants + are chosen externally to the protocol. Note that it is possible to + deploy the protocol without designating a single Coordinator; see + Section 7.5 for more information. + + FROST produces signatures that can be verified as if they were + produced from a single signer using a signing key s with + corresponding public key PK, where s is a Scalar value and PK = + G.ScalarBaseMult(s). As a threshold signing protocol, the group + signing key s is Shamir secret-shared amongst each of the + MAX_PARTICIPANTS participants and is used to produce signatures; see + Appendix C.1 for more information about Shamir secret sharing. In + particular, FROST assumes each participant is configured with the + following information: + + * An identifier, which is a NonZeroScalar value denoted as i in the + range [1, MAX_PARTICIPANTS] and MUST be distinct from the + identifier of every other participant. + + * A signing key sk_i, which is a Scalar value representing the i-th + Shamir secret share of the group signing key s. In particular, + sk_i is the value f(i) on a secret polynomial f of degree + (MIN_PARTICIPANTS - 1), where s is f(0). The public key + corresponding to this signing key share is PK_i = + G.ScalarBaseMult(sk_i). + + Additionally, the Coordinator and each participant are configured + with common group information, denoted as "group info," which + consists of the following: + + * Group public key, which is an Element in G denoted as PK. + + * Public keys PK_i for each participant, which are Element values in + G denoted as PK_i for each i in [1, MAX_PARTICIPANTS]. + + This document does not specify how this information, including the + signing key shares, are configured and distributed to participants. + In general, two configuration mechanisms are possible: one that + requires a single trusted dealer and one that requires performing a + distributed key generation protocol. We highlight the key generation + mechanism by a trusted dealer in Appendix C for reference. + + FROST requires two rounds to complete. In the first round, + participants generate and publish one-time-use commitments to be used + in the second round. In the second round, each participant produces + a share of the signature over the Coordinator-chosen message and the + other participant commitments. After the second round is completed, + the Coordinator aggregates the signature shares to produce a final + signature. The Coordinator SHOULD abort the protocol if the + signature is invalid; see Section 5.4 for more information about + dealing with invalid signatures and misbehaving participants. This + complete interaction (without being aborted) is shown in Figure 1. + + (group info) (group info, (group info, + | signing key share) signing key share) + | | | + v v v + Coordinator Signer-1 ... Signer-n + ------------------------------------------------------------ + signing request + ------------> + | + == Round 1 (Commitment) == + | participant commitment | | + |<-----------------------+ | + | ... | + | participant commitment (commit state) ==\ + |<-----------------------------------------+ | + | + == Round 2 (Signature Share Generation) == | + message + ------------> + | | + | participant input | | | + +------------------------> | | + | signature share | | | + |<-----------------------+ | | + | ... | | + | participant input | | + +------------------------------------------> / + | signature share |<=======/ + <------------------------------------------+ + | + == Aggregation == + | + signature | + <-----------+ + + Figure 1: FROST Protocol Overview + + Details for round one are described in Section 5.1 and details for + round two are described in Section 5.2. Note that each participant + persists some state between the two rounds; this state is deleted as + described in Section 5.2. The final Aggregation step is described in + Section 5.3. + + FROST assumes that all inputs to each round, especially those that + are received over the network, are validated before use. In + particular, this means that any value of type Element or Scalar + received over the network MUST be deserialized using + DeserializeElement and DeserializeScalar, respectively, as these + functions perform the necessary input validation steps. + Additionally, all messages sent over the wire MUST be encoded using + their respective functions, e.g., Scalars and Elements are encoded + using SerializeScalar and SerializeElement. + + FROST assumes reliable message delivery between the Coordinator and + participants in order for the protocol to complete. An attacker + masquerading as another participant will result only in an invalid + signature; see Section 7. However, in order to identify misbehaving + participants, we assume that the network channel is additionally + authenticated; confidentiality is not required. + +5.1. Round One - Commitment + + Round one involves each participant generating nonces and their + corresponding public commitments. A nonce is a pair of Scalar + values, and a commitment is a pair of Element values. Each + participant's behavior in this round is described by the commit + function below. Note that this function invokes nonce_generate + twice, once for each type of nonce produced. The output of this + function is a pair of secret nonces (hiding_nonce, binding_nonce) and + their corresponding public commitments (hiding_nonce_commitment, + binding_nonce_commitment). + + Inputs: + - sk_i, the secret key share, a Scalar. + + Outputs: + - (nonce, comm), a tuple of nonce and nonce commitment pairs, + where each value in the nonce pair is a Scalar and each value in + the nonce commitment pair is an Element. + + def commit(sk_i): + hiding_nonce = nonce_generate(sk_i) + binding_nonce = nonce_generate(sk_i) + hiding_nonce_commitment = G.ScalarBaseMult(hiding_nonce) + binding_nonce_commitment = G.ScalarBaseMult(binding_nonce) + nonces = (hiding_nonce, binding_nonce) + comms = (hiding_nonce_commitment, binding_nonce_commitment) + return (nonces, comms) + + The outputs nonce and comm from participant P_i are both stored + locally and kept for use in the second round. The nonce value is + secret and MUST NOT be shared, whereas the public output comm is sent + to the Coordinator. The nonce values produced by this function MUST + NOT be used in more than one invocation of sign, and the nonces MUST + be generated from a source of secure randomness. + +5.2. Round Two - Signature Share Generation + + In round two, the Coordinator is responsible for sending the message + to be signed and choosing the participants that will participate (a + number of at least MIN_PARTICIPANTS). Signers additionally require + locally held data, specifically their private key and the nonces + corresponding to their commitment issued in round one. + + The Coordinator begins by sending each participant the message to be + signed along with the set of signing commitments for all participants + in the participant list. Each participant MUST validate the inputs + before processing the Coordinator's request. In particular, the + signer MUST validate commitment_list, deserializing each group + Element in the list using DeserializeElement from Section 3.1. If + deserialization fails, the signer MUST abort the protocol. Moreover, + each participant MUST ensure that its identifier and commitments + (from the first round) appear in commitment_list. Applications that + restrict participants from processing arbitrary input messages are + also required to perform relevant application-layer input validation + checks; see Section 7.7 for more details. + + Upon receipt and successful input validation, each signer then runs + the following procedure to produce its own signature share. + + Inputs: + - identifier, identifier i of the participant, a NonZeroScalar. + - sk_i, signer secret key share, a Scalar. + - group_public_key, public key corresponding to the group signing + key, an Element. + - nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) + generated in round one. + - msg, the message to be signed, a byte string. + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + + + Outputs: + - sig_share, a signature share, a Scalar. + + def sign(identifier, sk_i, group_public_key, + nonce_i, msg, commitment_list): + # Compute the binding factor(s) + binding_factor_list = compute_binding_factors(group_public_key, + commitment_list, msg) + binding_factor = binding_factor_for_participant( + binding_factor_list, identifier) + + # Compute the group commitment + group_commitment = compute_group_commitment( + commitment_list, binding_factor_list) + + # Compute the interpolating value + participant_list = participants_from_commitment_list( + commitment_list) + lambda_i = derive_interpolating_value(participant_list, identifier) + + # Compute the per-message challenge + challenge = compute_challenge( + group_commitment, group_public_key, msg) + + # Compute the signature share + (hiding_nonce, binding_nonce) = nonce_i + sig_share = hiding_nonce + (binding_nonce * binding_factor) + + (lambda_i * sk_i * challenge) + + return sig_share + + The output of this procedure is a signature share. Each participant + sends these shares back to the Coordinator. Each participant MUST + delete the nonce and corresponding commitment after completing sign + and MUST NOT use the nonce as input more than once to sign. + + Note that the lambda_i value derived during this procedure does not + change across FROST signing operations for the same signing group. + As such, participants can compute it once and store it for reuse + across signing sessions. + +5.3. Signature Share Aggregation + + After participants perform round two and send their signature shares + to the Coordinator, the Coordinator aggregates each share to produce + a final signature. Before aggregating, the Coordinator MUST validate + each signature share using DeserializeScalar. If validation fails, + the Coordinator MUST abort the protocol, as the resulting signature + will be invalid. If all signature shares are valid, the Coordinator + aggregates them to produce the final signature using the following + procedure. + + Inputs: + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + - msg, the message to be signed, a byte string. + - group_public_key, public key corresponding to the group signing + key, an Element. + - sig_shares, a set of signature shares z_i, Scalar values, for each + participant, of length NUM_PARTICIPANTS, where + MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS. + + Outputs: + - (R, z), a Schnorr signature consisting of an Element R and + Scalar z. + + def aggregate(commitment_list, msg, group_public_key, sig_shares): + # Compute the binding factors + binding_factor_list = compute_binding_factors(group_public_key, + commitment_list, msg) + + # Compute the group commitment + group_commitment = compute_group_commitment( + commitment_list, binding_factor_list) + + # Compute aggregated signature + z = Scalar(0) + for z_i in sig_shares: + z = z + z_i + return (group_commitment, z) + + The output from the aggregation step is the output signature (R, z). + The canonical encoding of this signature is specified in Section 6. + + The Coordinator SHOULD verify this signature using the group public + key before publishing or releasing the signature. Signature + verification is as specified for the corresponding ciphersuite; see + Section 6 for details. The aggregate signature will verify + successfully if all signature shares are valid. Moreover, subsets of + valid signature shares will not yield a valid aggregate signature + themselves. + + If the aggregate signature verification fails, the Coordinator MAY + verify each signature share individually to identify and act on + misbehaving participants. The mechanism for acting on a misbehaving + participant is out of scope for this specification; see Section 5.4 + for more information about dealing with invalid signatures and + misbehaving participants. + + The function for verifying a signature share, denoted as + verify_signature_share, is described below. Recall that the + Coordinator is configured with "group info" that contains the group + public key PK and public keys PK_i for each participant. The + group_public_key and PK_i function arguments MUST come from that + previously stored group info. + + Inputs: + - identifier, identifier i of the participant, a NonZeroScalar. + - PK_i, the public key for the i-th participant, where + PK_i = G.ScalarBaseMult(sk_i), an Element. + - comm_i, pair of Element values in G + (hiding_nonce_commitment, binding_nonce_commitment) generated in + round one from the i-th participant. + - sig_share_i, a Scalar value indicating the signature share as + produced in round two from the i-th participant. + - commitment_list = [(i, hiding_nonce_commitment_i, + binding_nonce_commitment_i), ...], a list of commitments issued by + each participant, where each element in the list indicates a + NonZeroScalar identifier i and two commitment Element values + (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list + MUST be sorted in ascending order by identifier. + - group_public_key, public key corresponding to the group signing + key, an Element. + - msg, the message to be signed, a byte string. + + Outputs: + - True if the signature share is valid, and False otherwise. + + def verify_signature_share( + identifier, PK_i, comm_i, sig_share_i, commitment_list, + group_public_key, msg): + # Compute the binding factors + binding_factor_list = compute_binding_factors(group_public_key, + commitment_list, msg) + binding_factor = binding_factor_for_participant( + binding_factor_list, identifier) + + # Compute the group commitment + group_commitment = compute_group_commitment( + commitment_list, binding_factor_list) + + # Compute the commitment share + (hiding_nonce_commitment, binding_nonce_commitment) = comm_i + comm_share = hiding_nonce_commitment + G.ScalarMult( + binding_nonce_commitment, binding_factor) + + # Compute the challenge + challenge = compute_challenge( + group_commitment, group_public_key, msg) + + # Compute the interpolating value + participant_list = participants_from_commitment_list( + commitment_list) + lambda_i = derive_interpolating_value(participant_list, identifier) + + # Compute relation values + l = G.ScalarBaseMult(sig_share_i) + r = comm_share + G.ScalarMult(PK_i, challenge * lambda_i) + + return l == r + + The Coordinator can verify each signature share before aggregating + and verifying the signature under the group public key. However, + since the aggregate signature is valid if all signature shares are + valid, this order of operations is more expensive if the signature is + valid. + +5.4. Identifiable Abort + + FROST does not provide robustness; i.e, all participants are required + to complete the protocol honestly in order to generate a valid + signature. When the signing protocol does not produce a valid + signature, the Coordinator SHOULD abort; see Section 7 for more + information about FROST's security properties and the threat model. + + As a result of this property, a misbehaving participant can cause a + denial of service (DoS) on the signing protocol by contributing + malformed signature shares or refusing to participate. Identifying + misbehaving participants that produce invalid shares can be done by + checking signature shares from each participant using + verify_signature_share as described in Section 5.3. FROST assumes + the network channel is authenticated to identify the signer that + misbehaved. FROST allows for identifying misbehaving participants + that produce invalid signature shares as described in Section 5.3. + FROST does not provide accommodations for identifying participants + that refuse to participate, though applications are assumed to detect + when participants fail to engage in the signing protocol. + + In both cases, preventing this type of attack requires the + Coordinator to identify misbehaving participants such that + applications can take corrective action. The mechanism for acting on + misbehaving participants is out of scope for this specification. + However, one reasonable approach would be to remove the misbehaving + participant from the set of allowed participants in future runs of + FROST. + +6. Ciphersuites + + A FROST ciphersuite must specify the underlying prime-order group + details and cryptographic hash function. Each ciphersuite is denoted + as (Group, Hash), e.g., (ristretto255, SHA-512). This section + contains some ciphersuites. Each ciphersuite also includes a context + string, denoted as contextString, which is an ASCII string literal + (with no terminating NUL character). + + The RECOMMENDED ciphersuite is (ristretto255, SHA-512) as described + in Section 6.2. The (Ed25519, SHA-512) and (Ed448, SHAKE256) + ciphersuites are included for compatibility with Ed25519 and Ed448 as + defined in [RFC8032]. + + The DeserializeElement and DeserializeScalar functions instantiated + for a particular prime-order group corresponding to a ciphersuite + MUST adhere to the description in Section 3.1. Validation steps for + these functions are described for each of the ciphersuites below. + Future ciphersuites MUST describe how input validation is done for + DeserializeElement and DeserializeScalar. + + Each ciphersuite includes explicit instructions for verifying + signatures produced by FROST. Note that these instructions are + equivalent to those produced by a single participant. + + Each ciphersuite adheres to the requirements in Section 6.6. Future + ciphersuites MUST also adhere to these requirements. + +6.1. FROST(Ed25519, SHA-512) + + This ciphersuite uses edwards25519 for the Group and SHA-512 for the + hash function H meant to produce Ed25519-compliant signatures as + specified in Section 5.1 of [RFC8032]. The value of the + contextString parameter is "FROST-ED25519-SHA512-v1". + + Group: edwards25519 [RFC8032], where Ne = 32 and Ns = 32. + + Order(): Return 2^252 + 27742317777372353535851937790883648493 + (see [RFC7748]). + + Identity(): As defined in [RFC7748]. + + RandomScalar(): Implemented by returning a uniformly random + Scalar in the range [0, G.Order() - 1]. Refer to Appendix D + for implementation guidance. + + SerializeElement(A): Implemented as specified in [RFC8032], + Section 5.1.2. Additionally, this function validates that the + input element is not the group identity element. + + DeserializeElement(buf): Implemented as specified in [RFC8032], + Section 5.1.3. Additionally, this function validates that the + resulting element is not the group identity element and is in + the prime-order subgroup. If any of these checks fail, + deserialization returns an error. The latter check can be + implemented by multiplying the resulting point by the order of + the group and checking that the result is the identity element. + Note that optimizations for this check exist; see [Pornin22]. + + SerializeScalar(s): Implemented by outputting the little-endian + 32-byte encoding of the Scalar value with the top three bits + set to zero. + + DeserializeScalar(buf): Implemented by attempting to deserialize + a Scalar from a little-endian 32-byte string. This function + can fail if the input does not represent a Scalar in the range + [0, G.Order() - 1]. Note that this means the top three bits of + the input MUST be zero. + + Hash (H): SHA-512, which has an output of 64 bytes. + + H1(m): Implemented by computing H(contextString || "rho" || m), + interpreting the 64-byte digest as a little-endian integer, and + reducing the resulting integer modulo 2^252 + + 27742317777372353535851937790883648493. + + H2(m): Implemented by computing H(m), interpreting the 64-byte + digest as a little-endian integer, and reducing the resulting + integer modulo 2^252 + 27742317777372353535851937790883648493. + + H3(m): Implemented by computing H(contextString || "nonce" || m), + interpreting the 64-byte digest as a little-endian integer, and + reducing the resulting integer modulo 2^252 + + 27742317777372353535851937790883648493. + + H4(m): Implemented by computing H(contextString || "msg" || m). + + H5(m): Implemented by computing H(contextString || "com" || m). + + Normally, H2 would also include a domain separator; however, for + compatibility with [RFC8032], it is omitted. + + Signature verification is as specified in Section 5.1.7 of [RFC8032] + with the constraint that implementations MUST check the group + equation [8][z]B = [8]R + [8][c]PK (changed to use the notation in + this document). + + Canonical signature encoding is as specified in Appendix A. + +6.2. FROST(ristretto255, SHA-512) + + This ciphersuite uses ristretto255 for the Group and SHA-512 for the + hash function H. The value of the contextString parameter is "FROST- + RISTRETTO255-SHA512-v1". + + Group: ristretto255 [RISTRETTO], where Ne = 32 and Ns = 32. + + Order(): Return 2^252 + 27742317777372353535851937790883648493 + (see [RISTRETTO]). + + Identity(): As defined in [RISTRETTO]. + + RandomScalar(): Implemented by returning a uniformly random + Scalar in the range [0, G.Order() - 1]. Refer to Appendix D + for implementation guidance. + + SerializeElement(A): Implemented using the "Encode" function from + [RISTRETTO]. Additionally, this function validates that the + input element is not the group identity element. + + DeserializeElement(buf): Implemented using the "Decode" function + from [RISTRETTO]. Additionally, this function validates that + the resulting element is not the group identity element. If + either the "Decode" function or the check fails, + deserialization returns an error. + + SerializeScalar(s): Implemented by outputting the little-endian + 32-byte encoding of the Scalar value with the top three bits + set to zero. + + DeserializeScalar(buf): Implemented by attempting to deserialize + a Scalar from a little-endian 32-byte string. This function + can fail if the input does not represent a Scalar in the range + [0, G.Order() - 1]. Note that this means the top three bits of + the input MUST be zero. + + Hash (H): SHA-512, which has 64 bytes of output. + + H1(m): Implemented by computing H(contextString || "rho" || m) + and mapping the output to a Scalar as described in [RISTRETTO], + Section 4.4. + + H2(m): Implemented by computing H(contextString || "chal" || m) + and mapping the output to a Scalar as described in [RISTRETTO], + Section 4.4. + + H3(m): Implemented by computing H(contextString || "nonce" || m) + and mapping the output to a Scalar as described in [RISTRETTO], + Section 4.4. + + H4(m): Implemented by computing H(contextString || "msg" || m). + + H5(m): Implemented by computing H(contextString || "com" || m). + + Signature verification is as specified in Appendix B. + + Canonical signature encoding is as specified in Appendix A. + +6.3. FROST(Ed448, SHAKE256) + + This ciphersuite uses edwards448 for the Group and SHAKE256 for the + hash function H meant to produce Ed448-compliant signatures as + specified in Section 5.2 of [RFC8032]. Unlike Ed448 in [RFC8032], + this ciphersuite does not allow applications to specify a context + string and always sets the context of [RFC8032] to the empty string. + Note that this ciphersuite does not allow applications to specify a + context string as is allowed for Ed448 in [RFC8032], and always sets + the [RFC8032] context string to the empty string. The value of the + (internal to FROST) contextString parameter is "FROST- + ED448-SHAKE256-v1". + + Group: edwards448 [RFC8032], where Ne = 57 and Ns = 57. + + Order(): Return 2^446 - 13818066809895115352007386748515426880336 + 692474882178609894547503885. + + Identity(): As defined in [RFC7748]. + + RandomScalar(): Implemented by returning a uniformly random + Scalar in the range [0, G.Order() - 1]. Refer to Appendix D + for implementation guidance. + + SerializeElement(A): Implemented as specified in [RFC8032], + Section 5.2.2. Additionally, this function validates that the + input element is not the group identity element. + + DeserializeElement(buf): Implemented as specified in [RFC8032], + Section 5.2.3. Additionally, this function validates that the + resulting element is not the group identity element and is in + the prime-order subgroup. If any of these checks fail, + deserialization returns an error. The latter check can be + implemented by multiplying the resulting point by the order of + the group and checking that the result is the identity element. + Note that optimizations for this check exist; see [Pornin22]. + + SerializeScalar(s): Implemented by outputting the little-endian + 57-byte encoding of the Scalar value. + + DeserializeScalar(buf): Implemented by attempting to deserialize + a Scalar from a little-endian 57-byte string. This function + can fail if the input does not represent a Scalar in the range + [0, G.Order() - 1]. + + Hash (H): SHAKE256 with 114 bytes of output. + + H1(m): Implemented by computing H(contextString || "rho" || m), + interpreting the 114-byte digest as a little-endian integer, + and reducing the resulting integer modulo 2^446 - 1381806680989 + 5115352007386748515426880336692474882178609894547503885. + + H2(m): Implemented by computing H("SigEd448" || 0 || 0 || m), + interpreting the 114-byte digest as a little-endian integer, + and reducing the resulting integer modulo 2^446 - 1381806680989 + 5115352007386748515426880336692474882178609894547503885. + + H3(m): Implemented by computing H(contextString || "nonce" || m), + interpreting the 114-byte digest as a little-endian integer, + and reducing the resulting integer modulo 2^446 - 1381806680989 + 5115352007386748515426880336692474882178609894547503885. + + H4(m): Implemented by computing H(contextString || "msg" || m). + + H5(m): Implemented by computing H(contextString || "com" || m). + + Normally, H2 would also include a domain separator. However, it is + omitted for compatibility with [RFC8032]. + + Signature verification is as specified in Section 5.2.7 of [RFC8032] + with the constraint that implementations MUST check the group + equation [4][z]B = [4]R + [4][c]PK (changed to use the notation in + this document). + + Canonical signature encoding is as specified in Appendix A. + +6.4. FROST(P-256, SHA-256) + + This ciphersuite uses P-256 for the Group and SHA-256 for the hash + function H. The value of the contextString parameter is "FROST- + P256-SHA256-v1". + + Group: P-256 (secp256r1) [x9.62], where Ne = 33 and Ns = 32. + + Order(): Return 0xffffffff00000000ffffffffffffffffbce6faada7179e8 + 4f3b9cac2fc632551. + + Identity(): As defined in [x9.62]. + + RandomScalar(): Implemented by returning a uniformly random + Scalar in the range [0, G.Order() - 1]. Refer to Appendix D + for implementation guidance. + + SerializeElement(A): Implemented using the compressed Elliptic- + Curve-Point-to-Octet-String method according to [SEC1], + yielding a 33-byte output. Additionally, this function + validates that the input element is not the group identity + element. + + DeserializeElement(buf): Implemented by attempting to deserialize + a 33-byte input string to a public key using the compressed + Octet-String-to-Elliptic-Curve-Point method according to [SEC1] + and then performing public key validation as defined in + Section 3.2.2.1 of [SEC1]. This includes checking that the + coordinates of the resulting point are in the correct range, + that the point is on the curve, and that the point is not the + point at infinity. (As noted in the specification, validation + of the point order is not required since the cofactor is 1.) + If any of these checks fail, deserialization returns an error. + + SerializeScalar(s): Implemented using the Field-Element-to-Octet- + String conversion according to [SEC1]. + + DeserializeScalar(buf): Implemented by attempting to deserialize + a Scalar from a 32-byte string using Octet-String-to-Field- + Element from [SEC1]. This function can fail if the input does + not represent a Scalar in the range [0, G.Order() - 1]. + + Hash (H): SHA-256, which has 32 bytes of output. + + H1(m): Implemented as hash_to_field(m, 1) (see [HASH-TO-CURVE], + Section 5.2) using expand_message_xmd with SHA-256 with + parameters DST = contextString || "rho", F set to the Scalar + field, p set to G.Order(), m = 1, and L = 48. + + H2(m): Implemented as hash_to_field(m, 1) (see [HASH-TO-CURVE], + Section 5.2) using expand_message_xmd with SHA-256 with + parameters DST = contextString || "chal", F set to the Scalar + field, p set to G.Order(), m = 1, and L = 48. + + H3(m): Implemented as hash_to_field(m, 1) (see [HASH-TO-CURVE], + Section 5.2) using expand_message_xmd with SHA-256 with + parameters DST = contextString || "nonce", F set to the Scalar + field, p set to G.Order(), m = 1, and L = 48. + + H4(m): Implemented by computing H(contextString || "msg" || m). + + H5(m): Implemented by computing H(contextString || "com" || m). + + Signature verification is as specified in Appendix B. + + Canonical signature encoding is as specified in Appendix A. + +6.5. FROST(secp256k1, SHA-256) + + This ciphersuite uses secp256k1 for the Group and SHA-256 for the + hash function H. The value of the contextString parameter is "FROST- + secp256k1-SHA256-v1". + + Group: secp256k1 [SEC2], where Ne = 33 and Ns = 32. + + Order(): Return 0xfffffffffffffffffffffffffffffffebaaedce6af48a03 + bbfd25e8cd0364141. + + Identity(): As defined in [SEC2]. + + RandomScalar(): Implemented by returning a uniformly random + Scalar in the range [0, G.Order() - 1]. Refer to Appendix D + for implementation guidance. + + SerializeElement(A): Implemented using the compressed Elliptic- + Curve-Point-to-Octet-String method according to [SEC1], + yielding a 33-byte output. Additionally, this function + validates that the input element is not the group identity + element. + + DeserializeElement(buf): Implemented by attempting to deserialize + a 33-byte input string to a public key using the compressed + Octet-String-to-Elliptic-Curve-Point method according to [SEC1] + and then performing public key validation as defined in + Section 3.2.2.1 of [SEC1]. This includes checking that the + coordinates of the resulting point are in the correct range, + the point is on the curve, and the point is not the point at + infinity. (As noted in the specification, validation of the + point order is not required since the cofactor is 1.) If any + of these checks fail, deserialization returns an error. + + SerializeScalar(s): Implemented using the Field-Element-to-Octet- + String conversion according to [SEC1]. + + DeserializeScalar(buf): Implemented by attempting to deserialize + a Scalar from a 32-byte string using Octet-String-to-Field- + Element from [SEC1]. This function can fail if the input does + not represent a Scalar in the range [0, G.Order() - 1]. + + Hash (H): SHA-256, which has 32 bytes of output. + + H1(m): Implemented as hash_to_field(m, 1) (see [HASH-TO-CURVE], + Section 5.2) using expand_message_xmd with SHA-256 with + parameters DST = contextString || "rho", F set to the Scalar + field, p set to G.Order(), m = 1, and L = 48. + + H2(m): Implemented as hash_to_field(m, 1) (see [HASH-TO-CURVE], + Section 5.2) using expand_message_xmd with SHA-256 with + parameters DST = contextString || "chal", F set to the Scalar + field, p set to G.Order(), m = 1, and L = 48. + + H3(m): Implemented as hash_to_field(m, 1) (see [HASH-TO-CURVE], + Section 5.2) using expand_message_xmd with SHA-256 with + parameters DST = contextString || "nonce", F set to the Scalar + field, p set to G.Order(), m = 1, and L = 48. + + H4(m): Implemented by computing H(contextString || "msg" || m). + + H5(m): Implemented by computing H(contextString || "com" || m). + + Signature verification is as specified in Appendix B. + + Canonical signature encoding is as specified in Appendix A. + +6.6. Ciphersuite Requirements + + Future documents that introduce new ciphersuites MUST adhere to the + following requirements. + + 1. H1, H2, and H3 all have output distributions that are close to + (indistinguishable from) the uniform distribution. + + 2. All hash functions MUST be domain-separated with a per-suite + context string. Note that the FROST(Ed25519, SHA-512) + ciphersuite does not adhere to this requirement for H2 alone in + order to maintain compatibility with [RFC8032]. + + 3. The group MUST be of prime order and all deserialization + functions MUST output elements that belong to their respective + sets of Elements or Scalars, or else fail. + + 4. The canonical signature encoding details are clearly specified. + +7. Security Considerations + + A security analysis of FROST is documented in [FROST20] and + [StrongerSec22]. At a high level, FROST provides security against + Existential Unforgeability Under Chosen Message Attacks (EUF-CMA) as + defined in [StrongerSec22]. To satisfy this requirement, the + ciphersuite needs to adhere to the requirements in Section 6.6 and + the following assumptions must hold. + + * The signer key shares are generated and distributed securely, + e.g., via a trusted dealer that performs key generation (see + Appendix C.2) or through a distributed key generation protocol. + + * The Coordinator and at most (MIN_PARTICIPANTS-1) participants may + be corrupted. + + Note that the Coordinator is not trusted with any private + information, and communication at the time of signing can be + performed over a public channel as long as it is authenticated and + reliable. + + FROST provides security against DoS attacks under the following + assumptions: + + * The Coordinator does not perform a DoS attack. + + * The Coordinator identifies misbehaving participants such that they + can be removed from future invocations of FROST. The Coordinator + may also abort upon detecting a misbehaving participant to ensure + that invalid signatures are not produced. + + FROST does not aim to achieve the following goals: + + * Post-quantum security. FROST, like plain Schnorr signatures, + requires the hardness of the Discrete Logarithm Problem. + + * Robustness. Preventing DoS attacks against misbehaving + participants requires the Coordinator to identify and act on + misbehaving participants; see Section 5.4 for more information. + While FROST does not provide robustness, [ROAST] is a wrapper + protocol around FROST that does. + + * Downgrade prevention. All participants in the protocol are + assumed to agree on which algorithms to use. + + * Metadata protection. If protection for metadata is desired, a + higher-level communication channel can be used to facilitate key + generation and signing. + + The rest of this section documents issues particular to + implementations or deployments. + +7.1. Side-Channel Mitigations + + Several routines process secret values (nonces, signing keys / + shares), and depending on the implementation and deployment + environment, mitigating side-channels may be pertinent. Mitigating + these side-channels requires implementing G.ScalarMult(), + G.ScalarBaseMult(), G.SerializeScalar(), and G.DeserializeScalar() in + constant (value-independent) time. The various ciphersuites lend + themselves differently to specific implementation techniques and ease + of achieving side-channel resistance, though ultimately avoiding + value-dependent computation or branching is the goal. + +7.2. Optimizations + + [StrongerSec22] presented an optimization to FROST that reduces the + total number of Scalar multiplications from linear in the number of + signing participants to a constant. However, as described in + [StrongerSec22], this optimization removes the guarantee that the set + of signer participants that started round one of the protocol is the + same set of signing participants that produced the signature output + by round two. As such, the optimization is NOT RECOMMENDED and is + not covered in this document. + +7.3. Nonce Reuse Attacks + + Section 4.1 describes the procedure that participants use to produce + nonces during the first round of signing. The randomness produced in + this procedure MUST be sampled uniformly at random. The resulting + nonces produced via nonce_generate are indistinguishable from values + sampled uniformly at random. This requirement is necessary to avoid + replay attacks initiated by other participants that allow for a + complete key-recovery attack. The Coordinator MAY further hedge + against nonce reuse attacks by tracking participant nonce commitments + used for a given group key at the cost of additional state. + +7.4. Protocol Failures + + We do not specify what implementations should do when the protocol + fails other than requiring the protocol to abort. Examples of viable + failures include when a verification check returns invalid or the + underlying transport failed to deliver the required messages. + +7.5. Removing the Coordinator Role + + In some settings, it may be desirable to omit the role of the + Coordinator entirely. Doing so does not change the security + implications of FROST; instead, it simply requires each participant + to communicate with all other participants. We loosely describe how + to perform FROST signing among participants without this coordinator + role. We assume that every participant receives a message to be + signed from an external source as input prior to performing the + protocol. + + Every participant begins by performing commit() as is done in the + setting where a Coordinator is used. However, instead of sending the + commitment to the Coordinator, every participant will publish this + commitment to every other participant. In the second round, + participants will already have sufficient information to perform + signing, and they will directly perform sign(). All participants + will then publish their signature shares to one another. After + having received all signature shares from all other participants, + each participant will then perform verify_signature_share and then + aggregate directly. + + The requirements for the underlying network channel remain the same + in the setting where all participants play the role of the + Coordinator, in that all exchanged messages are public and the + channel must be reliable. However, in the setting where a player + attempts to split the view of all other players by sending disjoint + values to a subset of players, the signing operation will output an + invalid signature. To avoid this DoS, implementations may wish to + define a mechanism where messages are authenticated so that cheating + players can be identified and excluded. + +7.6. Input Message Hashing + + FROST signatures do not pre-hash message inputs. This means that the + entire message must be known in advance of invoking the signing + protocol. Applications can apply pre-hashing in settings where + storing the full message is prohibitively expensive. In such cases, + pre-hashing MUST use a collision-resistant hash function with a + security level commensurate with the security inherent to the + ciphersuite chosen. For applications that choose to apply pre- + hashing, it is RECOMMENDED that they use the hash function (H) + associated with the chosen ciphersuite in a manner similar to how H4 + is defined. In particular, a different prefix SHOULD be used to + differentiate this pre-hash from H4. For example, if a fictional + protocol Quux decided to pre-hash its input messages, one possible + way to do so is via H(contextString || "Quux-pre-hash" || m). + +7.7. Input Message Validation + + Message validation varies by application. For example, some + applications may require that participants only process messages of a + certain structure. In digital currency applications, wherein + multiple participants may collectively sign a transaction, it is + reasonable to require each participant to check that the input + message is a syntactically valid transaction. + + As another example, some applications may require that participants + only process messages with permitted content according to some + policy. In digital currency applications, this might mean that a + transaction being signed is allowed and intended by the relevant + stakeholders. Another instance of this type of message validation is + in the context of [TLS], wherein implementations may use threshold + signing protocols to produce signatures of transcript hashes. In + this setting, signing participants might require the raw TLS + handshake messages to validate before computing the transcript hash + that is signed. + + In general, input message validation is an application-specific + consideration that varies based on the use case and threat model. + However, it is RECOMMENDED that applications take additional + precautions and validate inputs so that participants do not operate + as signing oracles for arbitrary messages. + +8. IANA Considerations + + This document has no IANA actions. + +9. References + +9.1. Normative References + + [HASH-TO-CURVE] + Faz-Hernandez, A., Scott, S., Sullivan, N., Wahby, R. S., + and C. A. Wood, "Hashing to Elliptic Curves", RFC 9380, + DOI 10.17487/RFC9380, August 2023, + . + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, + DOI 10.17487/RFC2119, March 1997, + . + + [RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital + Signature Algorithm (EdDSA)", RFC 8032, + DOI 10.17487/RFC8032, January 2017, + . + + [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC + 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, + May 2017, . + + [RISTRETTO] + de Valence, H., Grigg, J., Hamburg, M., Lovecruft, I., + Tankersley, G., and F. Valsorda, "The ristretto255 and + decaf448 Groups", RFC 9496, DOI 10.17487/RFC9496, December + 2023, . + + [SEC1] Standards for Efficient Cryptography, "SEC 1: Elliptic + Curve Cryptography", Version 2.0, May 2009, + . + + [SEC2] Standards for Efficient Cryptography, "SEC 2: Recommended + Elliptic Curve Domain Parameters", Version 2.0, January + 2010, . + + [x9.62] American National Standards Institute, "Public Key + Cryptography for the Financial Services Industry: the + Elliptic Curve Digital Signature Algorithm (ECDSA)", + ANSI X9.62-2005, November 2005. + +9.2. Informative References + + [FeldmanSecretSharing] + Feldman, P., "A practical scheme for non-interactive + verifiable secret sharing", IEEE, 28th Annual Symposium on + Foundations of Computer Science (sfcs 1987), + DOI 10.1109/sfcs.1987.4, October 1987, + . + + [FROST20] Komlo, C. and I. Goldberg, "FROST: Flexible Round- + Optimized Schnorr Threshold Signatures", December 2020, + . + + [MultExp] Connolly, D. and C. Gouvea, "Speeding up FROST with multi- + scalar multiplication", June 2023, . + + [Pornin22] Pornin, T., "Point-Halving and Subgroup Membership in + Twisted Edwards Curves", September 2022, + . + + [RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC 4086, + DOI 10.17487/RFC4086, June 2005, + . + + [RFC7748] Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves + for Security", RFC 7748, DOI 10.17487/RFC7748, January + 2016, . + + [ROAST] Ruffing, T., Ronge, V., Jin, E., Schneider-Bensch, J., and + D. Schröder, "ROAST: Robust Asynchronous Schnorr Threshold + Signatures", Paper 2022/550, DOI 10.1145/3548606, November + 2022, . + + [ShamirSecretSharing] + Shamir, A., "How to share a secret", Association for + Computing Machinery (ACM), Communications of the ACM, Vol. + 22, Issue 11, pp. 612-613, DOI 10.1145/359168.359176, + November 1979, . + + [StrongerSec22] + Bellare, M., Crites, E., Komlo, C., Maller, M., Tessaro, + S., and C. Zhu, "Better than Advertised Security for Non- + interactive Threshold Signatures", + DOI 10.1007/978-3-031-15985-5_18, August 2022, + . + + [TLS] Rescorla, E., "The Transport Layer Security (TLS) Protocol + Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, + . + +Appendix A. Schnorr Signature Encoding + + This section describes one possible canonical encoding of FROST + signatures. Using notation from Section 3 of [TLS], the encoding of + a FROST signature (R, z) is as follows: + + struct { + opaque R_encoded[Ne]; + opaque z_encoded[Ns]; + } Signature; + + Where Signature.R_encoded is G.SerializeElement(R), + Signature.z_encoded is G.SerializeScalar(z), and G is determined by + ciphersuite. + +Appendix B. Schnorr Signature Generation and Verification for Prime- + Order Groups + + This section contains descriptions of functions for generating and + verifying Schnorr signatures. It is included to complement the + routines present in [RFC8032] for prime-order groups, including + ristretto255, P-256, and secp256k1. The functions for generating and + verifying signatures are prime_order_sign and prime_order_verify, + respectively. + + The function prime_order_sign produces a Schnorr signature over a + message given a full secret signing key as input (as opposed to a key + share). + + Inputs: + - msg, message to sign, a byte string. + - sk, secret key, a Scalar. + + Outputs: + - (R, z), a Schnorr signature consisting of an Element R and + Scalar z. + + def prime_order_sign(msg, sk): + r = G.RandomScalar() + R = G.ScalarBaseMult(r) + PK = G.ScalarBaseMult(sk) + comm_enc = G.SerializeElement(R) + pk_enc = G.SerializeElement(PK) + challenge_input = comm_enc || pk_enc || msg + c = H2(challenge_input) + z = r + (c * sk) // Scalar addition and multiplication + return (R, z) + + The function prime_order_verify verifies Schnorr signatures with + validated inputs. Specifically, it assumes that the signature R + component and public key belong to the prime-order group. + + Inputs: + - msg, signed message, a byte string. + - sig, a tuple (R, z) output from signature generation. + - PK, public key, an Element. + + Outputs: + - True if signature is valid, and False otherwise. + + def prime_order_verify(msg, sig = (R, z), PK): + comm_enc = G.SerializeElement(R) + pk_enc = G.SerializeElement(PK) + challenge_input = comm_enc || pk_enc || msg + c = H2(challenge_input) + + l = G.ScalarBaseMult(z) + r = R + G.ScalarMult(PK, c) + return l == r + +Appendix C. Trusted Dealer Key Generation + + One possible key generation mechanism is to depend on a trusted + dealer, wherein the dealer generates a group secret s uniformly at + random and uses Shamir and Verifiable Secret Sharing + [ShamirSecretSharing] as described in Appendices C.1 and C.2 to + create secret shares of s, denoted as s_i for i = 1, ..., + MAX_PARTICIPANTS, to be sent to all MAX_PARTICIPANTS participants. + This operation is specified in the trusted_dealer_keygen algorithm. + The mathematical relation between the secret key s and the + MAX_PARTICIPANTS secret shares is formalized in the + secret_share_combine(shares) algorithm, defined in Appendix C.1. + + The dealer that performs trusted_dealer_keygen is trusted to 1) + generate good randomness, 2) delete secret values after distributing + shares to each participant, and 3) keep secret values confidential. + + Inputs: + - secret_key, a group secret, a Scalar, that MUST be derived from at + least Ns bytes of entropy. + - MAX_PARTICIPANTS, the number of shares to generate, an integer. + - MIN_PARTICIPANTS, the threshold of the secret sharing scheme, + an integer. + + Outputs: + - participant_private_keys, MAX_PARTICIPANTS shares of the secret + key s, each a tuple consisting of the participant identifier + (a NonZeroScalar) and the key share (a Scalar). + - group_public_key, public key corresponding to the group signing + key, an Element. + - vss_commitment, a vector commitment of Elements in G, to each of + the coefficients in the polynomial defined by secret_key_shares and + whose first element is G.ScalarBaseMult(s). + + def trusted_dealer_keygen( + secret_key, MAX_PARTICIPANTS, MIN_PARTICIPANTS): + # Generate random coefficients for the polynomial + coefficients = [] + for i in range(0, MIN_PARTICIPANTS - 1): + coefficients.append(G.RandomScalar()) + participant_private_keys, coefficients = secret_share_shard( + secret_key, coefficients, MAX_PARTICIPANTS) + vss_commitment = vss_commit(coefficients): + return participant_private_keys, vss_commitment[0], vss_commitment + + It is assumed that the dealer then sends one secret key share to each + of the NUM_PARTICIPANTS participants, along with vss_commitment. + After receiving their secret key share and vss_commitment, + participants MUST abort if they do not have the same view of + vss_commitment. The dealer can use a secure broadcast channel to + ensure each participant has a consistent view of this commitment. + Furthermore, each participant MUST perform + vss_verify(secret_key_share_i, vss_commitment) and abort if the check + fails. The trusted dealer MUST delete the secret_key and + secret_key_shares upon completion. + + Use of this method for key generation requires a mutually + authenticated secure channel between the dealer and participants to + send secret key shares, wherein the channel provides confidentiality + and integrity. Mutually authenticated TLS is one possible deployment + option. + +C.1. Shamir Secret Sharing + + In Shamir secret sharing, a dealer distributes a secret Scalar s to n + participants in such a way that any cooperating subset of at least + MIN_PARTICIPANTS participants can recover the secret. There are two + basic steps in this scheme: 1) splitting a secret into multiple + shares and 2) combining shares to reveal the resulting secret. + + This secret sharing scheme works over any field F. In this + specification, F is the Scalar field of the prime-order group G. + + The procedure for splitting a secret into shares is as follows. The + algorithm polynomial_evaluate is defined in Appendix C.1.1. + + Inputs: + - s, secret value to be shared, a Scalar. + - coefficients, an array of size MIN_PARTICIPANTS - 1 with randomly + generated Scalars, not including the 0th coefficient of the + polynomial. + - MAX_PARTICIPANTS, the number of shares to generate, an integer less + than the group order. + + Outputs: + - secret_key_shares, A list of MAX_PARTICIPANTS number of secret + shares, each a tuple consisting of the participant identifier + (a NonZeroScalar) and the key share (a Scalar). + - coefficients, a vector of MIN_PARTICIPANTS coefficients which + uniquely determine a polynomial f. + + def secret_share_shard(s, coefficients, MAX_PARTICIPANTS): + # Prepend the secret to the coefficients + coefficients = [s] + coefficients + + # Evaluate the polynomial for each point x=1,...,n + secret_key_shares = [] + for x_i in range(1, MAX_PARTICIPANTS + 1): + y_i = polynomial_evaluate(Scalar(x_i), coefficients) + secret_key_share_i = (x_i, y_i) + secret_key_shares.append(secret_key_share_i) + return secret_key_shares, coefficients + + Let points be the output of this function. The i-th element in + points is the share for the i-th participant, which is the randomly + generated polynomial evaluated at coordinate i. We denote a secret + share as the tuple (i, points[i]) and the list of these shares as + shares. i MUST never equal 0; recall that f(0) = s, where f is the + polynomial defined in a Shamir secret sharing operation. + + The procedure for combining a shares list of length MIN_PARTICIPANTS + to recover the secret s is as follows; the algorithm + polynomial_interpolate_constant is defined in Appendix C.1.1. + + Inputs: + - shares, a list of at minimum MIN_PARTICIPANTS secret shares, each a + tuple (i, f(i)) where i and f(i) are Scalars. + + Outputs: + - s, the resulting secret that was previously split into shares, + a Scalar. + + Errors: + - "invalid parameters", if fewer than MIN_PARTICIPANTS input shares + are provided. + + def secret_share_combine(shares): + if len(shares) < MIN_PARTICIPANTS: + raise "invalid parameters" + s = polynomial_interpolate_constant(shares) + return s + +C.1.1. Additional Polynomial Operations + + This section describes two functions. One function, denoted as + polynomial_evaluate, is for evaluating a polynomial f(x) at a + particular point x using Horner's method, i.e., computing y = f(x). + The other function, polynomial_interpolate_constant, is for + recovering the constant term of an interpolating polynomial defined + by a set of points. + + The function polynomial_evaluate is defined as follows. + + Inputs: + - x, input at which to evaluate the polynomial, a Scalar + - coeffs, the polynomial coefficients, a list of Scalars + + Outputs: Scalar result of the polynomial evaluated at input x + + def polynomial_evaluate(x, coeffs): + value = Scalar(0) + for coeff in reverse(coeffs): + value *= x + value += coeff + return value + + The function polynomial_interpolate_constant is defined as follows. + + Inputs: + - points, a set of t points with distinct x coordinates on + a polynomial f, each a tuple of two Scalar values representing the + x and y coordinates. + + Outputs: + - f_zero, the constant term of f, i.e., f(0), a Scalar. + + def polynomial_interpolate_constant(points): + x_coords = [] + for (x, y) in points: + x_coords.append(x) + + f_zero = Scalar(0) + for (x, y) in points: + delta = y * derive_interpolating_value(x_coords, x) + f_zero += delta + + return f_zero + +C.2. Verifiable Secret Sharing + + Feldman's Verifiable Secret Sharing (VSS) [FeldmanSecretSharing] + builds upon Shamir secret sharing, adding a verification step to + demonstrate the consistency of a participant's share with a public + commitment to the polynomial f for which the secret s is the constant + term. This check ensures that all participants have a point (their + share) on the same polynomial, ensuring that they can reconstruct the + correct secret later. + + The procedure for committing to a polynomial f of degree at most + MIN_PARTICIPANTS-1 is as follows. + + Inputs: + - coeffs, a vector of the MIN_PARTICIPANTS coefficients that + uniquely determine a polynomial f. + + Outputs: + - vss_commitment, a vector commitment to each of the coefficients in + coeffs, where each item of the vector commitment is an Element. + + def vss_commit(coeffs): + vss_commitment = [] + for coeff in coeffs: + A_i = G.ScalarBaseMult(coeff) + vss_commitment.append(A_i) + return vss_commitment + + The procedure for verification of a participant's share is as + follows. If vss_verify fails, the participant MUST abort the + protocol, and the failure should be investigated out of band. + + Inputs: + - share_i: A tuple of the form (i, sk_i), where i indicates the + participant identifier (a NonZeroScalar), and sk_i the + participant's secret key, a secret share of the constant term of f, + where sk_i is a Scalar. + - vss_commitment, a VSS commitment to a secret polynomial f, a vector + commitment to each of the coefficients in coeffs, where each + element of the vector commitment is an Element. + + Outputs: + - True if sk_i is valid, and False otherwise. + + def vss_verify(share_i, vss_commitment) + (i, sk_i) = share_i + S_i = G.ScalarBaseMult(sk_i) + S_i' = G.Identity() + for j in range(0, MIN_PARTICIPANTS): + S_i' += G.ScalarMult(vss_commitment[j], pow(i, j)) + return S_i == S_i' + + We now define how the Coordinator and participants can derive group + info, which is an input into the FROST signing protocol. + + Inputs: + - MAX_PARTICIPANTS, the number of shares to generate, an integer. + - MIN_PARTICIPANTS, the threshold of the secret sharing scheme, + an integer. + - vss_commitment, a VSS commitment to a secret polynomial f, a vector + commitment to each of the coefficients in coeffs, where each + element of the vector commitment is an Element. + + Outputs: + - PK, the public key representing the group, an Element. + - participant_public_keys, a list of MAX_PARTICIPANTS public keys + PK_i for i=1,...,MAX_PARTICIPANTS, where each PK_i is the public + key, an Element, for participant i. + + def derive_group_info(MAX_PARTICIPANTS, MIN_PARTICIPANTS, + vss_commitment): + PK = vss_commitment[0] + participant_public_keys = [] + for i in range(1, MAX_PARTICIPANTS+1): + PK_i = G.Identity() + for j in range(0, MIN_PARTICIPANTS): + PK_i += G.ScalarMult(vss_commitment[j], pow(i, j)) + participant_public_keys.append(PK_i) + return PK, participant_public_keys + +Appendix D. Random Scalar Generation + + Two popular algorithms for generating a random integer uniformly + distributed in the range [0, G.Order() -1] are described in the + sections that follow. + +D.1. Rejection Sampling + + Generate a random byte array with Ns bytes and attempt to map to a + Scalar by calling DeserializeScalar in constant time. If it + succeeds, return the result. If it fails, try again with another + random byte array, until the procedure succeeds. Failure to + implement DeserializeScalar in constant time can leak information + about the underlying corresponding Scalar. + + As an optimization, if the group order is very close to a power of 2, + it is acceptable to omit the rejection test completely. In + particular, if the group order is p and there is an integer b such + that |p - 2^b| is less than 2^(b/2), then RandomScalar can simply + return a uniformly random integer of at most b bits. + +D.2. Wide Reduction + + Generate a random byte array with l = ceil(((3 * + ceil(log2(G.Order()))) / 2) / 8) bytes and interpret it as an + integer; reduce the integer modulo G.Order() and return the result. + See Section 5 of [HASH-TO-CURVE] for the underlying derivation of l. + +Appendix E. Test Vectors + + This section contains test vectors for all ciphersuites listed in + Section 6. All Element and Scalar values are represented in + serialized form and encoded in hexadecimal strings. Signatures are + represented as the concatenation of their constituent parts. The + input message to be signed is also encoded as a hexadecimal string. + + Each test vector consists of the following information. + + * Configuration. This lists the fixed parameters for the particular + instantiation of FROST, including MAX_PARTICIPANTS, + MIN_PARTICIPANTS, and NUM_PARTICIPANTS. + + * Group input parameters. This lists the group secret key and + shared public key, generated by a trusted dealer as described in + Appendix C, as well as the input message to be signed. The + randomly generated coefficients produced by the trusted dealer to + share the group signing secret are also listed. Each coefficient + is identified by its index, e.g., share_polynomial_coefficients[1] + is the coefficient of the first term in the polynomial. Note that + the 0-th coefficient is omitted, as this is equal to the group + secret key. All values are encoded as hexadecimal strings. + + * Signer input parameters. This lists the signing key share for + each of the NUM_PARTICIPANTS participants. + + * Round one parameters and outputs. This lists the NUM_PARTICIPANTS + participants engaged in the protocol, identified by their + NonZeroScalar identifier, and the following for each participant: + the hiding and binding commitment values produced in Section 5.1; + the randomness values used to derive the commitment nonces in + nonce_generate; the resulting group binding factor input computed + in part from the group commitment list encoded as described in + Section 4.3; and the group binding factor as computed in + Section 5.2. + + * Round two parameters and outputs. This lists the NUM_PARTICIPANTS + participants engaged in the protocol, identified by their + NonZeroScalar identifier, along with their corresponding output + signature share as produced in Section 5.2. + + * Final output. This lists the aggregate signature as produced in + Section 5.3. + +E.1. FROST(Ed25519, SHA-512) + + // Configuration information + MAX_PARTICIPANTS: 3 + MIN_PARTICIPANTS: 2 + NUM_PARTICIPANTS: 2 + + // Group input parameters + participant_list: 1,3 + group_secret_key: 7b1c33d3f5291d85de664833beb1ad469f7fb6025a0ec78b3a7 + 90c6e13a98304 + group_public_key: 15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040 + d380fb9738673 + message: 74657374 + share_polynomial_coefficients[1]: 178199860edd8c62f5212ee91eff1295d0d + 670ab4ed4506866bae57e7030b204 + + // Signer input parameters + P1 participant_share: 929dcc590407aae7d388761cddb0c0db6f5627aea8e217f + 4a033f2ec83d93509 + P2 participant_share: a91e66e012e4364ac9aaa405fcafd370402d9859f7b6685 + c07eed76bf409e80d + P3 participant_share: d3cb090a075eb154e82fdb4b3cb507f110040905468bb9c + 46da8bdea643a9a02 + + // Signer round one outputs + P1 hiding_nonce_randomness: 0fd2e39e111cdc266f6c0f4d0fd45c947761f1f5d + 3cb583dfcb9bbaf8d4c9fec + P1 binding_nonce_randomness: 69cd85f631d5f7f2721ed5e40519b1366f340a87 + c2f6856363dbdcda348a7501 + P1 hiding_nonce: 812d6104142944d5a55924de6d49940956206909f2acaeedecda + 2b726e630407 + P1 binding_nonce: b1110165fc2334149750b28dd813a39244f315cff14d4e89e61 + 42f262ed83301 + P1 hiding_nonce_commitment: b5aa8ab305882a6fc69cbee9327e5a45e54c08af6 + 1ae77cb8207be3d2ce13de3 + P1 binding_nonce_commitment: 67e98ab55aa310c3120418e5050c9cf76cf387cb + 20ac9e4b6fdb6f82a469f932 + P1 binding_factor_input: 15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3f + af66040d380fb9738673504df914fa965023fb75c25ded4bb260f417de6d32e5c442c + 6ba313791cc9a4948d6273e8d3511f93348ea7a708a9b862bc73ba2a79cfdfe07729a + 193751cbc973af46d8ac3440e518d4ce440a0e7d4ad5f62ca8940f32de6d8dc00fc12 + c660b817d587d82f856d277ce6473cae6d2f5763f7da2e8b4d799a3f3e725d4522ec7 + 0100000000000000000000000000000000000000000000000000000000000000 + P1 binding_factor: f2cb9d7dd9beff688da6fcc83fa89046b3479417f47f55600b + 106760eb3b5603 + P3 hiding_nonce_randomness: 86d64a260059e495d0fb4fcc17ea3da7452391baa + 494d4b00321098ed2a0062f + P3 binding_nonce_randomness: 13e6b25afb2eba51716a9a7d44130c0dbae0004a + 9ef8d7b5550c8a0e07c61775 + P3 hiding_nonce: c256de65476204095ebdc01bd11dc10e57b36bc96284595b8215 + 222374f99c0e + P3 binding_nonce: 243d71944d929063bc51205714ae3c2218bd3451d0214dfb5ae + ec2a90c35180d + P3 hiding_nonce_commitment: cfbdb165bd8aad6eb79deb8d287bcc0ab6658ae57 + fdcc98ed12c0669e90aec91 + P3 binding_nonce_commitment: 7487bc41a6e712eea2f2af24681b58b1cf1da278 + ea11fe4e8b78398965f13552 + P3 binding_factor_input: 15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3f + af66040d380fb9738673504df914fa965023fb75c25ded4bb260f417de6d32e5c442c + 6ba313791cc9a4948d6273e8d3511f93348ea7a708a9b862bc73ba2a79cfdfe07729a + 193751cbc973af46d8ac3440e518d4ce440a0e7d4ad5f62ca8940f32de6d8dc00fc12 + c660b817d587d82f856d277ce6473cae6d2f5763f7da2e8b4d799a3f3e725d4522ec7 + 0300000000000000000000000000000000000000000000000000000000000000 + P3 binding_factor: b087686bf35a13f3dc78e780a34b0fe8a77fef1b9938c563f5 + 573d71d8d7890f + + // Signer round two outputs + P1 sig_share: 001719ab5a53ee1a12095cd088fd149702c0720ce5fd2f29dbecf24 + b7281b603 + P3 sig_share: bd86125de990acc5e1f13781d8e32c03a9bbd4c53539bbc106058bf + d14326007 + + sig: 36282629c383bb820a88b71cae937d41f2f2adfcc3d02e55507e2fb9e2dd3cbe + bd9d2b0844e49ae0f3fa935161e1419aab7b47d21a37ebeae1f17d4987b3160b + +E.2. FROST(Ed448, SHAKE256) + + // Configuration information + MAX_PARTICIPANTS: 3 + MIN_PARTICIPANTS: 2 + NUM_PARTICIPANTS: 2 + + // Group input parameters + participant_list: 1,3 + group_secret_key: 6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b4 + 04158a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c00 + group_public_key: 3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d408 + 01ba265632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b000 + message: 74657374 + share_polynomial_coefficients[1]: dbd7a514f7a731976620f0436bd135fe8dd + dc3fadd6e0d13dbd58a1981e587d377d48e0b7ce4e0092967c5e85884d0275a7a740b + 6abdcd0500 + + // Signer input parameters + P1 participant_share: 4a2b2f5858a932ad3d3b18bd16e76ced3070d72fd79ae44 + 02df201f525e754716a1bc1b87a502297f2a99d89ea054e0018eb55d39562fd0100 + P2 participant_share: 2503d56c4f516444a45b080182b8a2ebbe4d9b2ab509f25 + 308c88c0ea7ccdc44e2ef4fc4f63403a11b116372438a1e287265cadeff1fcb0700 + P3 participant_share: 00db7a8146f995db0a7cf844ed89d8e94c2b5f259378ff6 + 6e39d172828b264185ac4decf7219e4aa4478285b9c0eef4fccdf3eea69dd980d00 + + // Signer round one outputs + P1 hiding_nonce_randomness: 9cda90c98863ef3141b75f09375757286b4bc323d + d61aeb45c07de45e4937bbd + P1 binding_nonce_randomness: 781bf4881ffe1aa06f9341a747179f07a49745f8 + cd37d4696f226aa065683c0a + P1 hiding_nonce: f922beb51a5ac88d1e862278d89e12c05263b945147db04b9566 + acb2b5b0f7422ccea4f9286f4f80e6b646e72143eeaecc0e5988f8b2b93100 + P1 binding_nonce: 1890f16a120cdeac092df29955a29c7cf29c13f6f7be60e63d6 + 3f3824f2d37e9c3a002dfefc232972dc08658a8c37c3ec06a0c5dc146150500 + P1 hiding_nonce_commitment: 3518c2246c874569e54ab254cb1da666ca30f7879 + 605cc43b4d2c47a521f8b5716080ab723d3a0cd04b7e41f3cc1d3031c94ccf3829b23 + fe80 + P1 binding_nonce_commitment: 11b3d5220c57d02057497de3c4eebab384900206 + 592d877059b0a5f1d5250d002682f0e22dff096c46bb81b46d60fcfe7752ed47cea76 + c3900 + P1 binding_factor_input: 3832f82fda00ff5365b0376df705675b63d2a93c24c6 + e81d40801ba265632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b00 + 0e9a0f30b97fe77ef751b08d4e252a3719ae9135e7f7926f7e3b7dd6656b27089ca35 + 4997fe5a633aa0946c89f022462e7e9d50fd6ef313f72d956ea4571089427daa1862f + 623a41625177d91e4a8f350ce9c8bd3bc7c766515dc1dd3a0eab93777526b616cccb1 + 48fe1e5992dc1ae705c8ba2f97ca8983328d41d375ed1e5fde5c9d672121c9e8f177f + 4a1a9b2575961531b33f054451363c8f27618382cd66ce14ad93b68dac6a09f5edcbc + cc813906b3fc50b8fef1cc09757b06646f38ceed1674cd6ced28a59c93851b325c6a9 + ef6a4b3b88860b7138ee246034561c7460db0b3fae501000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000 + P1 binding_factor: 71966390dfdbed73cf9b79486f3b70e23b243e6c40638fb559 + 98642a60109daecbfcb879eed9fe7dbbed8d9e47317715a5740f772173342e00 + P3 hiding_nonce_randomness: b3adf97ceea770e703ab295babf311d77e956a20d + 3452b4b3344aa89a828e6df + P3 binding_nonce_randomness: 81dbe7742b0920930299197322b255734e52bbb9 + 1f50cfe8ce689f56fadbce31 + P3 hiding_nonce: ccb5c1e82f23e0a4b966b824dbc7b0ef1cc5f56eeac2a4126e2b + 2143c5f3a4d890c52d27803abcf94927faf3fc405c0b2123a57a93cefa3b00 + P3 binding_nonce: e089df9bf311cf711e2a24ea27af53e07b846d09692fe11035a + 1112f04d8b7462a62f34d8c01493a22b57a1cbf1f0a46c77d64d46449a90100 + P3 hiding_nonce_commitment: 1254546d7d104c04e4fbcf29e05747e2edd392f67 + 87d05a6216f3713ef859efe573d180d291e48411e5e3006e9f90ee986ccc26b7a4249 + 0b80 + P3 binding_nonce_commitment: 3ef0cec20be15e56b3ddcb6f7b956fca0c8f7199 + 0f45316b537b4f64c5e8763e6629d7262ff7cd0235d0781f23be97bf8fa8817643ea1 + 9cd00 + P3 binding_factor_input: 3832f82fda00ff5365b0376df705675b63d2a93c24c6 + e81d40801ba265632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b00 + 0e9a0f30b97fe77ef751b08d4e252a3719ae9135e7f7926f7e3b7dd6656b27089ca35 + 4997fe5a633aa0946c89f022462e7e9d50fd6ef313f72d956ea4571089427daa1862f + 623a41625177d91e4a8f350ce9c8bd3bc7c766515dc1dd3a0eab93777526b616cccb1 + 48fe1e5992dc1ae705c8ba2f97ca8983328d41d375ed1e5fde5c9d672121c9e8f177f + 4a1a9b2575961531b33f054451363c8f27618382cd66ce14ad93b68dac6a09f5edcbc + cc813906b3fc50b8fef1cc09757b06646f38ceed1674cd6ced28a59c93851b325c6a9 + ef6a4b3b88860b7138ee246034561c7460db0b3fae503000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000 + P3 binding_factor: 236a6f7239ac2019334bad21323ec93bef2fead37bd5511435 + 6419f3fc1fb59f797f44079f28b1a64f51dd0a113f90f2c3a1c27d2faa4f1300 + + // Signer round two outputs + P1 sig_share: e1eb9bfbef792776b7103891032788406c070c5c315e3bf5d64acd4 + 6ea8855e85b53146150a09149665cbfec71626810b575e6f4dbe9ba3700 + P3 sig_share: 815434eb0b9f9242d54b8baf2141fe28976cabe5f441ccfcd5ee7cd + b4b52185b02b99e6de28e2ab086c7764068c5a01b5300986b9f084f3e00 + + sig: cd642cba59c449dad8e896a78a60e8edfcbd9040df524370891ff8077d47ce72 + 1d683874483795f0d85efcbd642c4510614328605a19c6ed806ffb773b6956419537c + dfdb2b2a51948733de192dcc4b82dc31580a536db6d435e0cb3ce322fbcf9ec23362d + da27092c08767e607bf2093600 + +E.3. FROST(ristretto255, SHA-512) + + // Configuration information + MAX_PARTICIPANTS: 3 + MIN_PARTICIPANTS: 2 + NUM_PARTICIPANTS: 2 + + // Group input parameters + participant_list: 1,3 + group_secret_key: 1b25a55e463cfd15cf14a5d3acc3d15053f08da49c8afcf3ab2 + 65f2ebc4f970b + group_public_key: e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d + 05083c7254f57 + message: 74657374 + share_polynomial_coefficients[1]: 410f8b744b19325891d73736923525a4f59 + 6c805d060dfb9c98009d34e3fec02 + + // Signer input parameters + P1 participant_share: 5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdba + d75a768010b8f830e + P2 participant_share: b06fc5eac20b4f6e1b271d9df2343d843e1e1fb03c4cbb6 + 73f2872d459ce6f01 + P3 participant_share: f17e505f0e2581c6acfe54d3846a622834b5e7b50cad9a2 + 109a97ba7a80d5c04 + + // Signer round one outputs + P1 hiding_nonce_randomness: f595a133b4d95c6e1f79887220c8b275ce6277e7f + 68a6640e1e7140f9be2fb5c + P1 binding_nonce_randomness: 34dd1001360e3513cb37bebfabe7be4a32c5bb91 + ba19fbd4360d039111f0fbdc + P1 hiding_nonce: 214f2cabb86ed71427ea7ad4283b0fae26b6746c801ce824b83c + eb2b99278c03 + P1 binding_nonce: c9b8f5e16770d15603f744f8694c44e335e8faef00dad182b8d + 7a34a62552f0c + P1 hiding_nonce_commitment: 965def4d0958398391fc06d8c2d72932608b1e625 + 5226de4fb8d972dac15fd57 + P1 binding_nonce_commitment: ec5170920660820007ae9e1d363936659ef622f9 + 9879898db86e5bf1d5bf2a14 + P1 binding_factor_input: e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3d + d9c3c0d05083c7254f572889dde2854e26377a16caf77dfee5f6be8fe5b4c80318da8 + 4698a4161021b033911db5ef8205362701bc9ecd983027814abee94f46d094943a2f4 + b79a6e4d4603e52c435d8344554942a0a472d8ad84320585b8da3ae5b9ce31cd1903f + 795c1af66de22af1a45f652cd05ee446b1b4091aaccc91e2471cd18a85a659cecd11f + 0100000000000000000000000000000000000000000000000000000000000000 + P1 binding_factor: 8967fd70fa06a58e5912603317fa94c77626395a695a0e4e4e + fc4476662eba0c + P3 hiding_nonce_randomness: daa0cf42a32617786d390e0c7edfbf2efbd428037 + 069357b5173ae61d6dd5d5e + P3 binding_nonce_randomness: b4387e72b2e4108ce4168931cc2c7fcce5f345a5 + 297368952c18b5fc8473f050 + P3 hiding_nonce: 3f7927872b0f9051dd98dd73eb2b91494173bbe0feb65a3e7e58 + d3e2318fa40f + P3 binding_nonce: ffd79445fb8030f0a3ddd3861aa4b42b618759282bfe24f1f93 + 04c7009728305 + P3 hiding_nonce_commitment: 480e06e3de182bf83489c45d7441879932fd7b434 + a26af41455756264fbd5d6e + P3 binding_nonce_commitment: 3064746dfd3c1862ef58fc68c706da287dd92506 + 6865ceacc816b3a28c7b363b + P3 binding_factor_input: e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3d + d9c3c0d05083c7254f572889dde2854e26377a16caf77dfee5f6be8fe5b4c80318da8 + 4698a4161021b033911db5ef8205362701bc9ecd983027814abee94f46d094943a2f4 + b79a6e4d4603e52c435d8344554942a0a472d8ad84320585b8da3ae5b9ce31cd1903f + 795c1af66de22af1a45f652cd05ee446b1b4091aaccc91e2471cd18a85a659cecd11f + 0300000000000000000000000000000000000000000000000000000000000000 + P3 binding_factor: f2c1bb7c33a10511158c2f1766a4a5fadf9f86f2a92692ed33 + 3128277cc31006 + + // Signer round two outputs + P1 sig_share: 9285f875923ce7e0c491a592e9ea1865ec1b823ead4854b48c8a462 + 87749ee09 + P3 sig_share: 7cb211fe0e3d59d25db6e36b3fb32344794139602a7b24f1ae0dc4e + 26ad7b908 + + sig: fc45655fbc66bbffad654ea4ce5fdae253a49a64ace25d9adb62010dd9fb2555 + 2164141787162e5b4cab915b4aa45d94655dbb9ed7c378a53b980a0be220a802 + +E.4. FROST(P-256, SHA-256) + + // Configuration information + MAX_PARTICIPANTS: 3 + MIN_PARTICIPANTS: 2 + NUM_PARTICIPANTS: 2 + + // Group input parameters + participant_list: 1,3 + group_secret_key: 8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735 + fa48b301bd8de + group_public_key: 023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4 + dc00585ec45ab70 + message: 74657374 + share_polynomial_coefficients[1]: 80f25e6c0709353e46bfbe882a11bdbb1f8 + 097e46340eb8673b7e14556e6c3a4 + + // Signer input parameters + P1 participant_share: 0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb + 9f35dbb0d8a9f7731 + P2 participant_share: 8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84 + 067159c52e1863ad5 + P3 participant_share: 0e80d6e8f6192c003b5488ce1eec8f5429587d48cf00154 + 1e713b2d53c09d928 + + // Signer round one outputs + P1 hiding_nonce_randomness: ec4c891c85fee802a9d757a67d1252e7f4e5efb8a + 538991ac18fbd0e06fb6fd3 + P1 binding_nonce_randomness: 9334e29d09061223f69a09421715a347e4e6deba + 77444c8f42b0c833f80f4ef9 + P1 hiding_nonce: 9f0542a5ba879a58f255c09f06da7102ef6a2dec6279700c656d + 58394d8facd4 + P1 binding_nonce: 6513dfe7429aa2fc972c69bb495b27118c45bbc6e654bb9dc9b + e55385b55c0d7 + P1 hiding_nonce_commitment: 0213b3e6298bf8ad46fd5e9389519a8665d63d98f + 4ec6a1fcca434e809d2d8070e + P1 binding_nonce_commitment: 02188ff1390bf69374d7b272e454b1878ef10a6b + 6ea3ff36f114b300b4dbd5233b + P1 binding_factor_input: 023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99c + fbd02b4dc00585ec45ab70825371853e974bc30ac5b947b216d70461919666584c70c + 51f9f56f117736c5d178dd0b521ad9c1abe98048419cbdec81504c85e12eb40e3bcb6 + ec73d3fc4afd000000000000000000000000000000000000000000000000000000000 + 0000001 + P1 binding_factor: 7925f0d4693f204e6e59233e92227c7124664a99739d2c06b8 + 1cf64ddf90559e + P3 hiding_nonce_randomness: c0451c5a0a5480d6c1f860e5db7d655233dca2669 + fd90ff048454b8ce983367b + P3 binding_nonce_randomness: 2ba5f7793ae700e40e78937a82f407dd35e847e3 + 3d1e607b5c7eb6ed2a8ed799 + P3 hiding_nonce: f73444a8972bcda9e506bbca3d2b1c083c10facdf4bb5d47fef7 + c2dc1d9f2a0d + P3 binding_nonce: 44c6a29075d6e7e4f8b97796205f9e22062e7835141470afe94 + 17fd317c1c303 + P3 hiding_nonce_commitment: 033ac9a5fe4a8b57316ba1c34e8a6de453033b750 + e8984924a984eb67a11e73a3f + P3 binding_nonce_commitment: 03a7a2480ee16199262e648aea3acab628a53e9b + 8c1945078f2ddfbdc98b7df369 + P3 binding_factor_input: 023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99c + fbd02b4dc00585ec45ab70825371853e974bc30ac5b947b216d70461919666584c70c + 51f9f56f117736c5d178dd0b521ad9c1abe98048419cbdec81504c85e12eb40e3bcb6 + ec73d3fc4afd000000000000000000000000000000000000000000000000000000000 + 0000003 + P3 binding_factor: e10d24a8a403723bcb6f9bb4c537f316593683b472f7a89f16 + 6630dde11822c4 + + // Signer round two outputs + P1 sig_share: 400308eaed7a2ddee02a265abe6a1cfe04d946ee8720768899619cf + abe7a3aeb + P3 sig_share: 561da3c179edbb0502d941bb3e3ace3c37d122aaa46fb54499f15f3 + a3331de44 + + sig: 026d8d434874f87bdb7bc0dfd239b2c00639044f9dcb195e9a04426f70bfa4b7 + 0d9620acac6767e8e3e3036815fca4eb3a3caa69992b902bcd3352fc34f1ac192f + +E.5. FROST(secp256k1, SHA-256) + + // Configuration information + MAX_PARTICIPANTS: 3 + MIN_PARTICIPANTS: 2 + NUM_PARTICIPANTS: 2 + + // Group input parameters + participant_list: 1,3 + group_secret_key: 0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bf + e7f0795a83114 + group_public_key: 02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f646 + 63b0eae87d87b4f + message: 74657374 + share_polynomial_coefficients[1]: fbf85eadae3058ea14f19148bb72b45e439 + 9c0b16028acaf0395c9b03c823579 + + // Signer input parameters + P1 participant_share: 08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310 + f5fc1ea2b01f4254c + P2 participant_share: 04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d8 + 2a385554e6e401984 + P3 participant_share: 00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f + 5e748c071da8c0dbc + + // Signer round one outputs + P1 hiding_nonce_randomness: 7ea5ed09af19f6ff21040c07ec2d2adbd35b759da + 5a401d4c99dd26b82391cb2 + P1 binding_nonce_randomness: 47acab018f116020c10cb9b9abdc7ac10aae1b48 + ca6e36dc15acb6ec9be5cdc5 + P1 hiding_nonce: 841d3a6450d7580b4da83c8e618414d0f024391f2aeb511d7579 + 224420aa81f0 + P1 binding_nonce: 8d2624f532af631377f33cf44b5ac5f849067cae2eacb88680a + 31e77c79b5a80 + P1 hiding_nonce_commitment: 03c699af97d26bb4d3f05232ec5e1938c12f1e6ae + 97643c8f8f11c9820303f1904 + P1 binding_nonce_commitment: 02fa2aaccd51b948c9dc1a325d77226e98a5a3fe + 65fe9ba213761a60123040a45e + P1 binding_factor_input: 02f37c34b66ced1fb51c34a90bdae006901f10625cc0 + 6c4f64663b0eae87d87b4fff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade + 6efac09a6513540fc3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9d + ef941b5d4801000000000000000000000000000000000000000000000000000000000 + 0000001 + P1 binding_factor: 3e08fe561e075c653cbfd46908a10e7637c70c74f0a77d5fd4 + 5d1a750c739ec6 + P3 hiding_nonce_randomness: e6cc56ccbd0502b3f6f831d91e2ebd01c4de0479e + 0191b66895a4ffd9b68d544 + P3 binding_nonce_randomness: 7203d55eb82a5ca0d7d83674541ab55f6e76f1b8 + 5391d2c13706a89a064fd5b9 + P3 hiding_nonce: 2b19b13f193f4ce83a399362a90cdc1e0ddcd83e57089a7af0bd + ca71d47869b2 + P3 binding_nonce: 7a443bde83dc63ef52dda354005225ba0e553243402a4705ce2 + 8ffaafe0f5b98 + P3 hiding_nonce_commitment: 03077507ba327fc074d2793955ef3410ee3f03b82 + b4cdc2370f71d865beb926ef6 + P3 binding_nonce_commitment: 02ad53031ddfbbacfc5fbda3d3b0c2445c8e3e99 + cbc4ca2db2aa283fa68525b135 + P3 binding_factor_input: 02f37c34b66ced1fb51c34a90bdae006901f10625cc0 + 6c4f64663b0eae87d87b4fff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade + 6efac09a6513540fc3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9d + ef941b5d4801000000000000000000000000000000000000000000000000000000000 + 0000003 + P3 binding_factor: 93f79041bb3fd266105be251adaeb5fd7f8b104fb554a4ba9a + 0becea48ddbfd7 + + // Signer round two outputs + P1 sig_share: c4fce1775a1e141fb579944166eab0d65eefe7b98d480a569bbbfcb + 14f91c197 + P3 sig_share: 0160fd0d388932f4826d2ebcd6b9eaba734f7c71cf25b4279a4ca25 + 81e47b18d + + sig: 0205b6d04d3774c8929413e3c76024d54149c372d57aae62574ed74319b5ea14 + d0c65dde8492a7471437e6c2fe3da49b90d23f642b5c6dbe7e36089f096dd97324 + +Acknowledgments + + This document was improved based on input and contributions by the + Zcash Foundation engineering team. In addition, the authors of this + document would like to thank Isis Lovecruft, Alden Torres, T. Wilson- + Brown, and Conrado Gouvea for their input and contributions. + +Authors' Addresses + + Deirdre Connolly + Zcash Foundation + Email: durumcrustulum@gmail.com + + + Chelsea Komlo + University of Waterloo, Zcash Foundation + Email: ckomlo@uwaterloo.ca + + + Ian Goldberg + University of Waterloo + Email: iang@uwaterloo.ca + + + Christopher A. Wood + Cloudflare + Email: caw@heapingbits.net -- cgit v1.2.3