1use anyhow::{anyhow, Context};
2use async_trait::async_trait;
3use slog::{debug, warn, Logger};
4
5use mithril_common::{
6 crypto_helper::{ProtocolAggregationError, ProtocolMultiSignature},
7 entities::{self},
8 logging::LoggerExtensions,
9 protocol::MultiSigner as ProtocolMultiSigner,
10 StdResult,
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 signatures: &entities::SingleSignatures,
25 ) -> StdResult<()>;
26
27 async fn verify_single_signature_for_next_stake_distribution(
29 &self,
30 message: &str,
31 signatures: &entities::SingleSignatures,
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 epoch_service: EpochServiceWrapper,
44 logger: Logger,
45}
46
47impl MultiSignerImpl {
48 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::SingleSignatures,
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 async fn verify_single_signature(
84 &self,
85 message: &str,
86 single_signature: &entities::SingleSignatures,
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::SingleSignatures,
100 ) -> StdResult<()> {
101 let epoch_service = self.epoch_service.read().await;
102 let next_protocol_multi_signer =
103 epoch_service
104 .next_protocol_multi_signer()
105 .with_context(|| {
106 "Multi Signer could not get next protocol multi-signer from epoch service"
107 })?;
108
109 self.run_verify_single_signature(message, single_signature, next_protocol_multi_signer)
110 }
111
112 async fn create_multi_signature(
114 &self,
115 open_message: &OpenMessage,
116 ) -> StdResult<Option<ProtocolMultiSignature>> {
117 debug!(self.logger, ">> create_multi_signature"; "open_message" => ?open_message);
118
119 let epoch_service = self.epoch_service.read().await;
120 let protocol_multi_signer = epoch_service.protocol_multi_signer().with_context(|| {
121 "Multi Signer could not get protocol multi-signer from epoch service"
122 })?;
123
124 match protocol_multi_signer.aggregate_single_signatures(
125 &open_message.single_signatures,
126 &open_message.protocol_message,
127 ) {
128 Ok(multi_signature) => Ok(Some(multi_signature)),
129 Err(ProtocolAggregationError::NotEnoughSignatures(actual, expected)) => {
130 warn!(
131 self.logger,
132 "Could not compute multi-signature: Not enough signatures. Got only {actual} out of {expected}."
133 );
134 Ok(None)
135 }
136 Err(err) => Err(anyhow!(err).context(format!(
137 "Multi Signer can not create multi-signature for entity type '{:?}'",
138 open_message.signed_entity_type
139 ))),
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use std::sync::Arc;
147 use tokio::sync::RwLock;
148
149 use mithril_common::crypto_helper::tests_setup::*;
150 use mithril_common::entities::{CardanoDbBeacon, Epoch, SignedEntityType, SignerWithStake};
151 use mithril_common::protocol::ToMessage;
152 use mithril_common::test_utils::{fake_data, MithrilFixtureBuilder};
153
154 use crate::entities::AggregatorEpochSettings;
155 use crate::services::{FakeEpochService, FakeEpochServiceBuilder};
156 use crate::test_tools::TestLogger;
157
158 use super::*;
159
160 fn take_signatures_until_quorum_is_almost_reached(
161 signatures: &mut Vec<entities::SingleSignatures>,
162 quorum: usize,
163 ) -> Vec<entities::SingleSignatures> {
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 assert!(multi_signer
292 .create_multi_signature(&open_message)
293 .await
294 .expect("create multi signature should not fail")
295 .is_none());
296
297 open_message.single_signatures = signatures_to_almost_reach_quorum;
299
300 assert!(multi_signer
301 .create_multi_signature(&open_message)
302 .await
303 .expect("create multi signature should not fail")
304 .is_none());
305
306 open_message.single_signatures.append(&mut signatures);
308
309 assert!(
310 multi_signer
311 .create_multi_signature(&open_message)
312 .await
313 .expect("create multi signature should not fail")
314 .is_some(),
315 "no multi-signature were computed"
316 );
317 }
318}