mithril_aggregator/
multi_signer.rs

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