The diagram below shows the signing process. Dashed lines represent data being sent through an authenticated communication channel.
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.
Each selected participant will then generate the nonces (a
their commitments (a
SigningCommitments) by calling
let (nonces, commitments) = frost::round1::commit( key_packages[&participant_identifier].signing_share(), &mut rng, );
SigningNonces must be kept by the participant to use in Round 2, while the
SigningCommitments must be sent to the Coordinator using an authenticated
The Coordinator will get all
SigningCommitments from the participants and the
message to be signed, and then build a
SigningPackage by calling
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);
SigningPackage must then be sent to all the participants using an
channel. (Of course,
if the message is confidential, then the channel must also be confidential.)
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".
Upon receiving the
SigningPackage, each participant will then produce their
signature share using their
KeyPackage from the key generation process and
SigningNonces from Round 1, by calling
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
SignatureShare must then be sent back to the Coordinator using
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.
Upon receiving the
SignatureShares from the participants, the Coordinator can
finally produce the final signature by calling
with the same
SigningPackage sent to the participants and the
PublicKeyPackage from the key generation (which is used to validate each
let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
The returned signature, a
Signature, will be a valid signature for the message
SigningPackage in Round 2 for the group verifying key in the
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.)
What should be done in that case is up to the application. The misbehaving participant could be excluded from future signing sessions, for example.
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
aggregate(). This just shows how the signature can be