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