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#[cfg_attr(test, mockall::automock)]
18#[async_trait]
19pub trait MultiSigner: Sync + Send {
20 async fn verify_single_signature(
22 &self,
23 message: &str,
24 signature: &entities::SingleSignature,
25 ) -> StdResult<()>;
26
27 async fn verify_single_signature_for_next_stake_distribution(
29 &self,
30 message: &str,
31 signature: &entities::SingleSignature,
32 ) -> StdResult<()>;
33
34 async fn create_multi_signature(
36 &self,
37 open_message: &OpenMessage,
38 ) -> StdResult<Option<ProtocolMultiSignature>>;
39}
40
41pub struct MultiSignerImpl {
43 aggregate_signature_type: AggregateSignatureType,
44 epoch_service: EpochServiceWrapper,
45 logger: Logger,
46}
47
48impl MultiSignerImpl {
49 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 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 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 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 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 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}