mithril_aggregator/
multi_signer.rs

1use anyhow::{Context, anyhow};
2use async_trait::async_trait;
3use slog::{Logger, debug, warn};
4
5use mithril_common::{
6    AggregateSignatureType, StdResult,
7    crypto_helper::{ProtocolAggregationError, ProtocolMultiSignature},
8    entities::{self},
9    logging::LoggerExtensions,
10    protocol::MultiSigner as ProtocolMultiSigner,
11};
12
13use crate::dependency_injection::EpochServiceWrapper;
14use crate::entities::OpenMessage;
15
16/// MultiSigner is the cryptographic engine in charge of producing multi signatures from individual signatures
17#[cfg_attr(test, mockall::automock)]
18#[async_trait]
19pub trait MultiSigner: Sync + Send {
20    /// Verify a single signature
21    async fn verify_single_signature(
22        &self,
23        message: &str,
24        signature: &entities::SingleSignature,
25    ) -> StdResult<()>;
26
27    /// Verify a single signature using the stake distribution of the next epoch
28    async fn verify_single_signature_for_next_stake_distribution(
29        &self,
30        message: &str,
31        signature: &entities::SingleSignature,
32    ) -> StdResult<()>;
33
34    /// Creates a multi signature from single signatures
35    async fn create_multi_signature(
36        &self,
37        open_message: &OpenMessage,
38    ) -> StdResult<Option<ProtocolMultiSignature>>;
39}
40
41/// MultiSignerImpl is an implementation of the MultiSigner
42pub struct MultiSignerImpl {
43    aggregate_signature_type: AggregateSignatureType,
44    epoch_service: EpochServiceWrapper,
45    logger: Logger,
46}
47
48impl MultiSignerImpl {
49    /// MultiSignerImpl factory
50    pub fn new(
51        aggregate_signature_type: AggregateSignatureType,
52        epoch_service: EpochServiceWrapper,
53        logger: Logger,
54    ) -> Self {
55        let logger = logger.new_with_component_name::<Self>();
56        debug!(logger, "New MultiSignerImpl created");
57        Self {
58            aggregate_signature_type,
59            epoch_service,
60            logger,
61        }
62    }
63
64    fn run_verify_single_signature(
65        &self,
66        message: &str,
67        single_signature: &entities::SingleSignature,
68        protocol_multi_signer: &ProtocolMultiSigner,
69    ) -> StdResult<()> {
70        debug!(
71            self.logger,
72            "Verify single signature from {} at indexes {:?} for message {:?}",
73            single_signature.party_id,
74            single_signature.won_indexes,
75            message
76        );
77
78        protocol_multi_signer
79            .verify_single_signature(&message, single_signature)
80            .with_context(|| {
81                format!("Multi Signer can not verify single signature for message '{message:?}'")
82            })
83    }
84}
85
86#[async_trait]
87impl MultiSigner for MultiSignerImpl {
88    /// Verify a single signature
89    async fn verify_single_signature(
90        &self,
91        message: &str,
92        single_signature: &entities::SingleSignature,
93    ) -> StdResult<()> {
94        let epoch_service = self.epoch_service.read().await;
95        let protocol_multi_signer = epoch_service.protocol_multi_signer().with_context(
96            || "Multi Signer could not get protocol multi-signer from epoch service",
97        )?;
98
99        self.run_verify_single_signature(message, single_signature, protocol_multi_signer)
100    }
101
102    async fn verify_single_signature_for_next_stake_distribution(
103        &self,
104        message: &str,
105        single_signature: &entities::SingleSignature,
106    ) -> StdResult<()> {
107        let epoch_service = self.epoch_service.read().await;
108        let next_protocol_multi_signer = epoch_service.next_protocol_multi_signer().with_context(
109            || "Multi Signer could not get next protocol multi-signer from epoch service",
110        )?;
111
112        self.run_verify_single_signature(message, single_signature, next_protocol_multi_signer)
113    }
114
115    /// Creates a multi signature from single signatures
116    async fn create_multi_signature(
117        &self,
118        open_message: &OpenMessage,
119    ) -> StdResult<Option<ProtocolMultiSignature>> {
120        debug!(self.logger, ">> create_multi_signature"; "open_message" => ?open_message);
121
122        let epoch_service = self.epoch_service.read().await;
123        let protocol_multi_signer = epoch_service.protocol_multi_signer().with_context(
124            || "Multi Signer could not get protocol multi-signer from epoch service",
125        )?;
126
127        match protocol_multi_signer.aggregate_single_signatures(
128            &open_message.single_signatures,
129            &open_message.protocol_message,
130            self.aggregate_signature_type,
131        ) {
132            Ok(multi_signature) => Ok(Some(multi_signature)),
133            Err(err) => match err.downcast_ref::<ProtocolAggregationError>() {
134                Some(ProtocolAggregationError::NotEnoughSignatures(actual, expected)) => {
135                    warn!(
136                        self.logger,
137                        "Could not compute multi-signature: Not enough signatures. Got only {actual} out of {expected}."
138                    );
139                    Ok(None)
140                }
141                _ => Err(anyhow!(err).context(format!(
142                    "Multi Signer can not create multi-signature for entity type '{:?}'",
143                    open_message.signed_entity_type
144                ))),
145            },
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use std::sync::Arc;
153    use tokio::sync::RwLock;
154
155    use mithril_common::entities::{CardanoDbBeacon, Epoch, SignedEntityType, SignerWithStake};
156    use mithril_common::protocol::ToMessage;
157    use mithril_common::test::{
158        builder::MithrilFixtureBuilder,
159        crypto_helper::setup_message,
160        double::{Dummy, fake_data},
161    };
162
163    use crate::entities::AggregatorEpochSettings;
164    use crate::services::{FakeEpochService, FakeEpochServiceBuilder};
165    use crate::test::TestLogger;
166
167    use super::*;
168
169    fn take_signatures_until_quorum_is_almost_reached(
170        signatures: &mut Vec<entities::SingleSignature>,
171        quorum: usize,
172    ) -> Vec<entities::SingleSignature> {
173        signatures.sort_by(|l, r| l.won_indexes.len().cmp(&r.won_indexes.len()));
174
175        let mut result = vec![];
176        let mut nb_won_indexes = 0;
177
178        while let Some(signature) = signatures.first() {
179            if signature.won_indexes.len() + nb_won_indexes >= quorum {
180                break;
181            }
182            nb_won_indexes += signature.won_indexes.len();
183            result.push(signatures.remove(0));
184        }
185
186        result
187    }
188
189    #[tokio::test]
190    async fn test_verify_single_signature() {
191        let epoch = Epoch(5);
192        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
193        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
194        let multi_signer = MultiSignerImpl::new(
195            AggregateSignatureType::default(),
196            Arc::new(RwLock::new(
197                FakeEpochServiceBuilder {
198                    current_epoch_settings: AggregatorEpochSettings {
199                        protocol_parameters: fixture.protocol_parameters(),
200                        ..AggregatorEpochSettings::dummy()
201                    },
202                    next_epoch_settings: AggregatorEpochSettings {
203                        protocol_parameters: next_fixture.protocol_parameters(),
204                        ..AggregatorEpochSettings::dummy()
205                    },
206                    signer_registration_epoch_settings: AggregatorEpochSettings {
207                        protocol_parameters: next_fixture.protocol_parameters(),
208                        ..AggregatorEpochSettings::dummy()
209                    },
210                    current_signers_with_stake: fixture.signers_with_stake(),
211                    next_signers_with_stake: next_fixture.signers_with_stake(),
212                    ..FakeEpochServiceBuilder::dummy(epoch)
213                }
214                .build(),
215            )),
216            TestLogger::stdout(),
217        );
218
219        {
220            let message = setup_message();
221            let signature = fixture.signers_fixture()[0].sign(&message).unwrap();
222
223            multi_signer
224                .verify_single_signature(&message.to_message(), &signature)
225                .await
226                .unwrap();
227
228            multi_signer.verify_single_signature_for_next_stake_distribution(&message.to_message(), &signature).await.expect_err(
229                "single signature issued in the current epoch should not be valid for the next epoch",
230            );
231        }
232        {
233            let message = setup_message();
234            let next_epoch_signature = next_fixture.signers_fixture()[0].sign(&message).unwrap();
235
236            multi_signer
237                .verify_single_signature_for_next_stake_distribution(
238                    &message.to_message(),
239                    &next_epoch_signature,
240                )
241                .await
242                .unwrap();
243
244            multi_signer.verify_single_signature(&message.to_message(), &next_epoch_signature).await.expect_err(
245                "single signature issued in the next epoch should not be valid for the current epoch",
246            );
247        }
248    }
249
250    #[tokio::test]
251    async fn test_multi_signer_multi_signature_ok() {
252        let epoch = Epoch(5);
253        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
254        let protocol_parameters = fixture.protocol_parameters();
255        let multi_signer = MultiSignerImpl::new(
256            AggregateSignatureType::default(),
257            Arc::new(RwLock::new(FakeEpochService::from_fixture(epoch, &fixture))),
258            TestLogger::stdout(),
259        );
260
261        let message = setup_message();
262
263        let mut signatures = Vec::new();
264
265        let mut expected_certificate_signers: Vec<SignerWithStake> = Vec::new();
266        for signer_fixture in fixture.signers_fixture() {
267            if let Some(signature) = signer_fixture.sign(&message) {
268                signatures.push(signature);
269                expected_certificate_signers.push(signer_fixture.signer_with_stake.to_owned())
270            }
271        }
272
273        for signature in &signatures {
274            multi_signer
275                .verify_single_signature(&message.to_message(), signature)
276                .await
277                .expect("single signature should be valid");
278        }
279
280        let signatures_to_almost_reach_quorum = take_signatures_until_quorum_is_almost_reached(
281            &mut signatures,
282            protocol_parameters.k as usize,
283        );
284        assert!(
285            !signatures_to_almost_reach_quorum.is_empty(),
286            "they should be at least one signature that can be registered without reaching the quorum"
287        );
288
289        let mut open_message = OpenMessage {
290            epoch,
291            signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon {
292                epoch,
293                ..fake_data::beacon()
294            }),
295            protocol_message: message.clone(),
296            is_certified: false,
297            single_signatures: Vec::new(),
298            ..OpenMessage::dummy()
299        };
300
301        // No signatures registered: multi-signer can't create the multi-signature
302        assert!(
303            multi_signer
304                .create_multi_signature(&open_message)
305                .await
306                .expect("create multi signature should not fail")
307                .is_none()
308        );
309
310        // Add some signatures but not enough to reach the quorum: multi-signer should not create the multi-signature
311        open_message.single_signatures = signatures_to_almost_reach_quorum;
312
313        assert!(
314            multi_signer
315                .create_multi_signature(&open_message)
316                .await
317                .expect("create multi signature should not fail")
318                .is_none()
319        );
320
321        // Add the remaining signatures to reach the quorum: multi-signer should create a multi-signature
322        open_message.single_signatures.append(&mut signatures);
323
324        assert!(
325            multi_signer
326                .create_multi_signature(&open_message)
327                .await
328                .expect("create multi signature should not fail")
329                .is_some(),
330            "no multi-signature were computed"
331        );
332    }
333}