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#[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";
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 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 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 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 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 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}