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::entities::{CardanoDbBeacon, Epoch, SignedEntityType, SignerWithStake};
147    use mithril_common::protocol::ToMessage;
148    use mithril_common::test::{
149        builder::MithrilFixtureBuilder,
150        crypto_helper::setup_message,
151        double::{Dummy, fake_data},
152    };
153
154    use crate::entities::AggregatorEpochSettings;
155    use crate::services::{FakeEpochService, FakeEpochServiceBuilder};
156    use crate::test::TestLogger;
157
158    use super::*;
159
160    fn take_signatures_until_quorum_is_almost_reached(
161        signatures: &mut Vec<entities::SingleSignature>,
162        quorum: usize,
163    ) -> Vec<entities::SingleSignature> {
164        signatures.sort_by(|l, r| l.won_indexes.len().cmp(&r.won_indexes.len()));
165
166        let mut result = vec![];
167        let mut nb_won_indexes = 0;
168
169        while let Some(signature) = signatures.first() {
170            if signature.won_indexes.len() + nb_won_indexes >= quorum {
171                break;
172            }
173            nb_won_indexes += signature.won_indexes.len();
174            result.push(signatures.remove(0));
175        }
176
177        result
178    }
179
180    #[tokio::test]
181    async fn test_verify_single_signature() {
182        let epoch = Epoch(5);
183        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
184        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
185        let multi_signer = MultiSignerImpl::new(
186            Arc::new(RwLock::new(
187                FakeEpochServiceBuilder {
188                    current_epoch_settings: AggregatorEpochSettings {
189                        protocol_parameters: fixture.protocol_parameters(),
190                        ..AggregatorEpochSettings::dummy()
191                    },
192                    next_epoch_settings: AggregatorEpochSettings {
193                        protocol_parameters: next_fixture.protocol_parameters(),
194                        ..AggregatorEpochSettings::dummy()
195                    },
196                    signer_registration_epoch_settings: AggregatorEpochSettings {
197                        protocol_parameters: next_fixture.protocol_parameters(),
198                        ..AggregatorEpochSettings::dummy()
199                    },
200                    current_signers_with_stake: fixture.signers_with_stake(),
201                    next_signers_with_stake: next_fixture.signers_with_stake(),
202                    ..FakeEpochServiceBuilder::dummy(epoch)
203                }
204                .build(),
205            )),
206            TestLogger::stdout(),
207        );
208
209        {
210            let message = setup_message();
211            let signature = fixture.signers_fixture()[0].sign(&message).unwrap();
212
213            multi_signer
214                .verify_single_signature(&message.to_message(), &signature)
215                .await
216                .unwrap();
217
218            multi_signer.verify_single_signature_for_next_stake_distribution(&message.to_message(), &signature).await.expect_err(
219                "single signature issued in the current epoch should not be valid for the next epoch",
220            );
221        }
222        {
223            let message = setup_message();
224            let next_epoch_signature = next_fixture.signers_fixture()[0].sign(&message).unwrap();
225
226            multi_signer
227                .verify_single_signature_for_next_stake_distribution(
228                    &message.to_message(),
229                    &next_epoch_signature,
230                )
231                .await
232                .unwrap();
233
234            multi_signer.verify_single_signature(&message.to_message(), &next_epoch_signature).await.expect_err(
235                "single signature issued in the next epoch should not be valid for the current epoch",
236            );
237        }
238    }
239
240    #[tokio::test]
241    async fn test_multi_signer_multi_signature_ok() {
242        let epoch = Epoch(5);
243        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
244        let protocol_parameters = fixture.protocol_parameters();
245        let multi_signer = MultiSignerImpl::new(
246            Arc::new(RwLock::new(FakeEpochService::from_fixture(epoch, &fixture))),
247            TestLogger::stdout(),
248        );
249
250        let message = setup_message();
251
252        let mut signatures = Vec::new();
253
254        let mut expected_certificate_signers: Vec<SignerWithStake> = Vec::new();
255        for signer_fixture in fixture.signers_fixture() {
256            if let Some(signature) = signer_fixture.sign(&message) {
257                signatures.push(signature);
258                expected_certificate_signers.push(signer_fixture.signer_with_stake.to_owned())
259            }
260        }
261
262        for signature in &signatures {
263            multi_signer
264                .verify_single_signature(&message.to_message(), signature)
265                .await
266                .expect("single signature should be valid");
267        }
268
269        let signatures_to_almost_reach_quorum = take_signatures_until_quorum_is_almost_reached(
270            &mut signatures,
271            protocol_parameters.k as usize,
272        );
273        assert!(
274            !signatures_to_almost_reach_quorum.is_empty(),
275            "they should be at least one signature that can be registered without reaching the quorum"
276        );
277
278        let mut open_message = OpenMessage {
279            epoch,
280            signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon {
281                epoch,
282                ..fake_data::beacon()
283            }),
284            protocol_message: message.clone(),
285            is_certified: false,
286            single_signatures: Vec::new(),
287            ..OpenMessage::dummy()
288        };
289
290        // No signatures registered: multi-signer can't create the multi-signature
291        assert!(
292            multi_signer
293                .create_multi_signature(&open_message)
294                .await
295                .expect("create multi signature should not fail")
296                .is_none()
297        );
298
299        // Add some signatures but not enough to reach the quorum: multi-signer should not create the multi-signature
300        open_message.single_signatures = signatures_to_almost_reach_quorum;
301
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 the remaining signatures to reach the quorum: multi-signer should create a multi-signature
311        open_message.single_signatures.append(&mut signatures);
312
313        assert!(
314            multi_signer
315                .create_multi_signature(&open_message)
316                .await
317                .expect("create multi signature should not fail")
318                .is_some(),
319            "no multi-signature were computed"
320        );
321    }
322}