mithril_common/protocol/
signer_builder.rs

1use anyhow::Context;
2use rand_chacha::ChaCha20Rng;
3use rand_core::{CryptoRng, RngCore, SeedableRng};
4use std::path::Path;
5use thiserror::Error;
6
7use crate::{
8    crypto_helper::{
9        ProtocolAggregateVerificationKey, ProtocolClerk, ProtocolClosedKeyRegistration,
10        ProtocolInitializer, ProtocolKeyRegistration, ProtocolStakeDistribution,
11    },
12    entities::{PartyId, ProtocolParameters, SignerWithStake},
13    protocol::MultiSigner,
14    StdResult,
15};
16
17use super::SingleSigner;
18
19/// Allow to build Single Or Multi signers to generate a single signature or aggregate them
20#[derive(Debug)]
21pub struct SignerBuilder {
22    protocol_parameters: ProtocolParameters,
23    closed_key_registration: ProtocolClosedKeyRegistration,
24}
25
26/// [SignerBuilder] specific errors
27#[derive(Debug, Error)]
28pub enum SignerBuilderError {
29    /// Error raised when the list of signers given to the builder is empty
30    #[error("The list of signers must not be empty to create a signer builder.")]
31    EmptySigners,
32}
33
34impl SignerBuilder {
35    /// [SignerBuilder] constructor.
36    pub fn new(
37        registered_signers: &[SignerWithStake],
38        protocol_parameters: &ProtocolParameters,
39    ) -> StdResult<Self> {
40        if registered_signers.is_empty() {
41            return Err(SignerBuilderError::EmptySigners.into());
42        }
43
44        let stake_distribution = registered_signers
45            .iter()
46            .map(|s| s.into())
47            .collect::<ProtocolStakeDistribution>();
48        let mut key_registration = ProtocolKeyRegistration::init(&stake_distribution);
49
50        for signer in registered_signers {
51            key_registration
52                .register(
53                    Some(signer.party_id.to_owned()),
54                    signer.operational_certificate.clone(),
55                    signer.verification_key_signature,
56                    signer.kes_period,
57                    signer.verification_key,
58                )
59                .with_context(|| {
60                    format!("Registration failed for signer: '{}'", signer.party_id)
61                })?;
62        }
63
64        let closed_registration = key_registration.close();
65
66        Ok(Self {
67            protocol_parameters: protocol_parameters.clone(),
68            closed_key_registration: closed_registration,
69        })
70    }
71
72    /// Build a [MultiSigner] based on the registered parties
73    pub fn build_multi_signer(&self) -> MultiSigner {
74        let stm_parameters = self.protocol_parameters.clone().into();
75        let clerk =
76            ProtocolClerk::from_registration(&stm_parameters, &self.closed_key_registration);
77
78        MultiSigner::new(clerk, stm_parameters)
79    }
80
81    /// Compute aggregate verification key from stake distribution
82    pub fn compute_aggregate_verification_key(&self) -> ProtocolAggregateVerificationKey {
83        let stm_parameters = self.protocol_parameters.clone().into();
84        let clerk =
85            ProtocolClerk::from_registration(&stm_parameters, &self.closed_key_registration);
86
87        clerk.compute_avk().into()
88    }
89
90    fn build_single_signer_with_rng<R: RngCore + CryptoRng>(
91        &self,
92        signer_with_stake: SignerWithStake,
93        kes_secret_key_path: Option<&Path>,
94        rng: &mut R,
95    ) -> StdResult<(SingleSigner, ProtocolInitializer)> {
96        let protocol_initializer = ProtocolInitializer::setup(
97            self.protocol_parameters.clone().into(),
98            kes_secret_key_path,
99            signer_with_stake.kes_period,
100            signer_with_stake.stake,
101            rng,
102        )
103        .with_context(|| {
104            format!(
105                "Could not create a protocol initializer for party: '{}'",
106                signer_with_stake.party_id
107            )
108        })?;
109
110        let protocol_signer = protocol_initializer
111            .clone()
112            .new_signer(self.closed_key_registration.clone())
113            .with_context(|| {
114                format!(
115                    "Could not create a protocol signer for party: '{}'",
116                    signer_with_stake.party_id
117                )
118            })?;
119
120        Ok((
121            SingleSigner::new(signer_with_stake.party_id, protocol_signer),
122            protocol_initializer,
123        ))
124    }
125
126    /// Build non deterministic [SingleSigner] and [ProtocolInitializer] based on the registered parties.
127    pub fn build_single_signer(
128        &self,
129        signer_with_stake: SignerWithStake,
130        kes_secret_key_path: Option<&Path>,
131    ) -> StdResult<(SingleSigner, ProtocolInitializer)> {
132        self.build_single_signer_with_rng(
133            signer_with_stake,
134            kes_secret_key_path,
135            &mut rand_core::OsRng,
136        )
137    }
138
139    /// Build deterministic [SingleSigner] and [ProtocolInitializer] based on the registered parties.
140    ///
141    /// Use for **TEST ONLY**.
142    pub fn build_test_single_signer(
143        &self,
144        signer_with_stake: SignerWithStake,
145        kes_secret_key_path: Option<&Path>,
146    ) -> StdResult<(SingleSigner, ProtocolInitializer)> {
147        let protocol_initializer_seed: [u8; 32] = signer_with_stake.party_id.as_bytes()[..32]
148            .try_into()
149            .unwrap();
150
151        self.build_single_signer_with_rng(
152            signer_with_stake,
153            kes_secret_key_path,
154            &mut ChaCha20Rng::from_seed(protocol_initializer_seed),
155        )
156    }
157
158    /// Restore a [SingleSigner] based on the registered parties and the given
159    /// protocol_initializer.
160    ///
161    /// This is useful since each protocol initializer holds a unique secret key
162    /// that corresponds to a registration key sent to an aggregator.
163    ///
164    /// The actual signing of message is done at a later epoch.
165    ///
166    /// The [SignerBuilder] used must be tied to the key registration, stake distribution
167    /// and protocol parameters of the epoch during which the given protocol initializer
168    /// was created.
169    pub fn restore_signer_from_initializer(
170        &self,
171        party_id: PartyId,
172        protocol_initializer: ProtocolInitializer,
173    ) -> StdResult<SingleSigner> {
174        let single_signer = protocol_initializer
175            .new_signer(self.closed_key_registration.clone())
176            .with_context(|| {
177                "Could not create a single signer from protocol initializer".to_string()
178            })?;
179
180        Ok(SingleSigner::new(party_id, single_signer))
181    }
182}
183
184#[cfg(test)]
185mod test {
186    use mithril_stm::RegisterError;
187
188    use crate::{
189        crypto_helper::ProtocolRegistrationErrorWrapper,
190        test_utils::{fake_data, MithrilFixtureBuilder},
191    };
192
193    use super::*;
194
195    #[test]
196    fn cant_construct_signer_builder_with_an_empty_signers_list() {
197        let signers = vec![];
198        let protocol_parameters = fake_data::protocol_parameters();
199
200        let error = SignerBuilder::new(&signers, &protocol_parameters).expect_err(
201            "We should not be able to construct a signer builder with an empty signers list",
202        );
203
204        match error.downcast_ref::<SignerBuilderError>() {
205            Some(SignerBuilderError::EmptySigners) => (),
206            _ => panic!("Expected an EmptySigners error, got: {error:?}"),
207        }
208    }
209
210    #[test]
211    fn cant_construct_signer_builder_if_a_signer_registration_fail() {
212        // To make this test fail we try to build a SignerBuilder with signers from two
213        // different stake distributions, this will pass the individual check but not the
214        // register check.
215        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
216        let fixture_with_another_stake_distribution = MithrilFixtureBuilder::default()
217            .with_signers(1)
218            .with_stake_distribution(
219                crate::test_utils::StakeDistributionGenerationMethod::RandomDistribution {
220                    seed: [4u8; 32],
221                    min_stake: 1,
222                },
223            )
224            .build();
225        let mut signers = fixture.signers_with_stake();
226        signers.append(&mut fixture_with_another_stake_distribution.signers_with_stake());
227
228        let error = SignerBuilder::new(&signers, &fixture.protocol_parameters()).expect_err(
229            "We should not be able to construct a signer builder if a signer registration fail",
230        );
231
232        match error.downcast_ref::<ProtocolRegistrationErrorWrapper>() {
233            Some(ProtocolRegistrationErrorWrapper::CoreRegister(_)) => (),
234            _ => panic!("Expected an CoreRegister error, got: {error:?}"),
235        }
236    }
237
238    #[test]
239    fn can_construct_signer_builder_with_valid_signers() {
240        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
241
242        SignerBuilder::new(
243            &fixture.signers_with_stake(),
244            &fixture.protocol_parameters(),
245        )
246        .expect("We should be able to construct a signer builder with valid signers");
247    }
248
249    #[test]
250    fn cant_build_single_signer_for_unregistered_party() {
251        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
252        let signers_from_another_fixture = MithrilFixtureBuilder::default()
253            .with_signers(1)
254            .with_party_id_seed([4u8; 32])
255            .build()
256            .signers_fixture();
257        let non_registered_signer = signers_from_another_fixture.first().unwrap();
258
259        let error = SignerBuilder::new(
260            &fixture.signers_with_stake(),
261            &fixture.protocol_parameters(),
262        )
263        .unwrap()
264        .build_test_single_signer(
265            non_registered_signer.signer_with_stake.clone(),
266            non_registered_signer.kes_secret_key_path(),
267        )
268        .expect_err(
269            "We should not be able to construct a single signer from a not registered party",
270        );
271
272        match error.downcast_ref::<ProtocolRegistrationErrorWrapper>() {
273            Some(ProtocolRegistrationErrorWrapper::CoreRegister(
274                RegisterError::UnregisteredInitializer,
275            )) => (),
276            _ => panic!(
277                "Expected an ProtocolRegistrationErrorWrapper::CoreRegister error, got: {error:?}"
278            ),
279        }
280    }
281
282    #[test]
283    fn should_build_single_signer_for_registered_party() {
284        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
285        let signers = fixture.signers_fixture();
286        let signer = signers.first().unwrap();
287
288        let builder = SignerBuilder::new(
289            &fixture.signers_with_stake(),
290            &fixture.protocol_parameters(),
291        )
292        .unwrap();
293
294        builder
295            .build_test_single_signer(
296                signer.signer_with_stake.clone(),
297                signer.kes_secret_key_path(),
298            )
299            .expect("Should be able to build test single signer for a registered party");
300    }
301
302    #[test]
303    fn should_restore_single_signer_from_previous_initializer() {
304        let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
305        let signers = fixture.signers_fixture();
306        let signer = signers.first().unwrap();
307
308        let first_builder = SignerBuilder::new(
309            &fixture.signers_with_stake(),
310            &fixture.protocol_parameters(),
311        )
312        .unwrap();
313
314        let (_, initializer) = first_builder
315            .build_test_single_signer(
316                signer.signer_with_stake.clone(),
317                signer.kes_secret_key_path(),
318            )
319            .unwrap();
320
321        let second_builder = SignerBuilder::new(
322            &fixture.signers_with_stake(),
323            &fixture.protocol_parameters(),
324        )
325        .unwrap();
326
327        second_builder
328            .restore_signer_from_initializer(signer.party_id(), initializer)
329            .expect("Should be able to restore a single signer from a protocol initialized");
330    }
331}