Signing
The diagram below shows the signing process. Dashed lines represent data being sent through an authenticated communication channel.

Coordinator, Round 1
To sign, the Coordinator must select which participants are going to generate the signature, and must signal to start the process. This needs to be implemented by users of the ZF FROST library and will depend on the communication channel being used.
Participants, Round 1
Each selected participant will then generate the nonces (a SigningNonces) and
their commitments (a SigningCommitments) by calling
round1::commit():
let (nonces, commitments) = frost::round1::commit(
key_package.signing_share(),
&mut rng,
);
The SigningNonces must be kept by the participant to use in Round 2, while the
SigningCommitments must be sent to the Coordinator.
FROST does not require using an authenticated nor encrypted channel during the signing process.
Coordinator, Round 2
The Coordinator will get all SigningCommitments from the participants and the
message to be signed, and then build a SigningPackage by calling
SigningPackage::new().
let message = "message to sign".as_bytes();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticate channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(commitments_map, message);
The SigningPackage must then be sent to all the participants. (If the message
is confidential, then the channel must also be confidential, since the message
is included in the SigningPackage.)
In all of the main FROST ciphersuites, the entire message must be sent to participants. In some cases, where the message is too big, it may be necessary to send a hash of the message instead. We strongly suggest creating a specific ciphersuite for this, and not just sending the hash as if it were the message. For reference, see how RFC 8032 handles "pre-hashing".
Participants, Round 2
Upon receiving the SigningPackage, each participant will then produce their
signature share using their KeyPackage from the key generation process and
their SigningNonces from Round 1, by calling
round2::sign():
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
The resulting SignatureShare must then be sent back to the Coordinator.
In most applications, it is important that the participant must be aware of what they are signing. Thus the application should show the message to the participant and obtain their consent to proceed before producing the signature share.
Coordinator, Aggregate
Upon receiving the SignatureShares from the participants, the Coordinator can
finally produce the final signature by calling
aggregate()
with the same SigningPackage sent to the participants and the
PublicKeyPackage from the key generation (which is used to validate each
SignatureShare).
let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
The returned signature, a Signature, will be a valid signature for the message
in the SigningPackage in Round 2 for the group verifying key in the PublicKeyPackage.
FROST supports identifiable abort: if a participant misbehaves and produces an
invalid signature share, then aggregation will fail and the returned error
will have the identifier of the misbehaving participant. (If multiple participants
misbehave, only the first one detected will be returned. If you need to detect
all cheaters, use aggregate_custom())
What should be done in that case is up to the application. The misbehaving participant could be excluded from future signing sessions, for example.
In aggregate() you need to provide a map from Identifier to
SignatureShare. If you need cheater detection, then it is important that these
identifiers come from a mapping between authenticated channels and identifiers;
i.e. you should not simply send the Identifier along with the
SignatureShare; otherwise the cheater could simply lie about their identifier.
For example, if you authenticate the communication channels with TLS, then you
will need to create a public key -> identifier mapping, and use that mapping
to get the identifier for the connection where the SignatureShare was read
from.
Verifying signatures
The Coordinator could verify the signature with:
let is_signature_valid = pubkey_package
.verifying_key()
.verify(message, &group_signature)
.is_ok();
(There is no need for the Coordinator to verify the signature since that already
happens inside aggregate(). This just shows how the signature can be
verified.)