Skip to main content

frost_bluepallas/
signing_utilities.rs

1//! Convenience functions to generate FROST signatures from various inputs.
2
3use alloc::collections::BTreeMap;
4
5use crate::{BluePallas, ChallengeMessage};
6use rand_core::{CryptoRng, RngCore};
7
8type SignResult<M> = Result<
9    (
10        frost_core::Signature<BluePallas<M>>,
11        frost_core::VerifyingKey<BluePallas<M>>,
12    ),
13    frost_core::Error<BluePallas<M>>,
14>;
15
16/// Helper function to sign a message using existing key packages
17pub fn sign_from_packages<M, R: RngCore + CryptoRng>(
18    message: &[u8],
19    shares: BTreeMap<
20        frost_core::Identifier<BluePallas<M>>,
21        frost_core::keys::SecretShare<BluePallas<M>>,
22    >,
23    pubkey_package: frost_core::keys::PublicKeyPackage<BluePallas<M>>,
24    mut rng: R,
25) -> SignResult<M>
26where
27    M: ChallengeMessage,
28{
29    let min_signers = pubkey_package.verifying_shares().len().min(3);
30
31    // Verifies the secret shares from the dealer and store them in a BTreeMap.
32    // In practice, the KeyPackages must be sent to its respective participants
33    // through a confidential and authenticated channel.
34    let mut key_packages: BTreeMap<_, _> = BTreeMap::new();
35
36    for (identifier, secret_share) in shares {
37        let key_package = frost_core::keys::KeyPackage::try_from(secret_share)?;
38        key_packages.insert(identifier, key_package);
39    }
40
41    let mut nonces_map = BTreeMap::new();
42    let mut commitments_map = BTreeMap::new();
43
44    ////////////////////////////////////////////////////////////////////////////
45    // Round 1: generating nonces and signing commitments for each participant
46    ////////////////////////////////////////////////////////////////////////////
47
48    // In practice, each iteration of this loop will be executed by its respective participant.
49    for participant_index in 1..=min_signers {
50        let participant_identifier = frost_core::Identifier::try_from(participant_index as u16)
51            .map_err(|_| frost_core::Error::MalformedIdentifier)?;
52        let key_package = &key_packages[&participant_identifier];
53        // Generate one (1) nonce and one SigningCommitments instance for each
54        // participant, up to _threshold_.
55        let (nonces, commitments) =
56            frost_core::round1::commit(key_package.signing_share(), &mut rng);
57        // In practice, the nonces must be kept by the participant to use in the
58        // next round, while the commitment must be sent to the coordinator
59        // (or to every other participant if there is no coordinator) using
60        // an authenticated channel.
61        nonces_map.insert(participant_identifier, nonces);
62        commitments_map.insert(participant_identifier, commitments);
63    }
64
65    // This is what the signature aggregator / coordinator needs to do:
66    // - decide what message to sign
67    // - take one (unused) commitment per signing participant
68    let mut signature_shares = BTreeMap::new();
69    let signing_package = frost_core::SigningPackage::new(commitments_map, message);
70
71    ////////////////////////////////////////////////////////////////////////////
72    // Round 2: each participant generates their signature share
73    ////////////////////////////////////////////////////////////////////////////
74
75    // In practice, each iteration of this loop will be executed by its respective participant.
76    for participant_identifier in nonces_map.keys() {
77        let key_package = &key_packages[participant_identifier];
78
79        let nonces = &nonces_map[participant_identifier];
80
81        // Each participant generates their signature share.
82        let signature_share = frost_core::round2::sign(&signing_package, nonces, key_package)?;
83
84        // In practice, the signature share must be sent to the Coordinator
85        // using an authenticated channel.
86        signature_shares.insert(*participant_identifier, signature_share);
87    }
88
89    ////////////////////////////////////////////////////////////////////////////
90    // Aggregation: collects the signing shares from all participants,
91    // generates the final signature.
92    ////////////////////////////////////////////////////////////////////////////
93
94    // Aggregate (also verifies the signature shares)
95    let group_signature =
96        frost_core::aggregate(&signing_package, &signature_shares, &pubkey_package)?;
97    let pk = pubkey_package.verifying_key();
98
99    Ok((group_signature, *pk))
100}
101
102/// Helper function which uses FROST to generate a signature, message and verifying key to use in tests.
103/// This uses trusted dealer rather than DKG
104pub fn generate_signature_random<M, R: RngCore + CryptoRng>(
105    message: &[u8],
106    mut rng: R,
107) -> SignResult<M>
108where
109    M: ChallengeMessage,
110{
111    let max_signers = 5;
112    let min_signers = 3;
113    let (shares, pubkey_package) = frost_core::keys::generate_with_dealer(
114        max_signers,
115        min_signers,
116        frost_core::keys::IdentifierList::Default,
117        &mut rng,
118    )?;
119
120    sign_from_packages(message, shares, pubkey_package, rng)
121}
122
123/// Helper function which splits an existing signing key into FROST shares and generates a signature.
124/// This uses the split function to create shares from a single signing key.
125pub fn generate_signature_from_sk<M, R: RngCore + CryptoRng>(
126    message: &[u8],
127    signing_key: &frost_core::SigningKey<BluePallas<M>>,
128    mut rng: R,
129) -> SignResult<M>
130where
131    M: ChallengeMessage,
132{
133    let max_signers = 5;
134    let min_signers = 3;
135    let (shares, pubkey_package) = frost_core::keys::split(
136        signing_key,
137        max_signers,
138        min_signers,
139        frost_core::keys::IdentifierList::Default,
140        &mut rng,
141    )?;
142
143    sign_from_packages(message, shares, pubkey_package, rng)
144}