mithril_aggregator/
multi_signer.rs

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